library(Seurat)
library(SnapATAC)
Loading required package: Matrix
Loading required package: rhdf5
library(tidyverse)
[30m── [1mAttaching packages[22m ───────────────────────────────────────────────────────────────────────────────────── tidyverse 1.2.1 ──[39m
[30m[32m✔[30m [34mggplot2[30m 3.2.1 [32m✔[30m [34mpurrr [30m 0.3.3
[32m✔[30m [34mtibble [30m 2.1.3 [32m✔[30m [34mdplyr [30m 0.8.3
[32m✔[30m [34mtidyr [30m 1.0.0 [32m✔[30m [34mstringr[30m 1.4.0
[32m✔[30m [34mreadr [30m 1.3.1 [32m✔[30m [34mforcats[30m 0.4.0[39m
[30m── [1mConflicts[22m ──────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
[31m✖[30m [34mtidyr[30m::[32mexpand()[30m masks [34mMatrix[30m::expand()
[31m✖[30m [34mdplyr[30m::[32mfilter()[30m masks [34mstats[30m::filter()
[31m✖[30m [34mdplyr[30m::[32mlag()[30m masks [34mstats[30m::lag()
[31m✖[30m [34mtidyr[30m::[32mpack()[30m masks [34mMatrix[30m::pack()
[31m✖[30m [34mtidyr[30m::[32munpack()[30m masks [34mMatrix[30m::unpack()[39m
library(DescTools) # 4 AUC function
Registered S3 method overwritten by 'DescTools':
method from
reorder.factor gdata
library(glue)
Attaching package: ‘glue’
The following object is masked from ‘package:dplyr’:
collapse
library(ggalluvial) # 4 river plot
library(ggpubr)
Loading required package: magrittr
Attaching package: ‘magrittr’
The following object is masked from ‘package:purrr’:
set_names
The following object is masked from ‘package:tidyr’:
extract
source("~/multiOmic_benchmark/utils.R")
gg_color_hue <- function(n) {
hues = seq(15, 375, length = n + 1)
hcl(h = hues, l = 65, c = 100)[1:n]
}
## Make output directory
outdir <- "~/multiOmic_benchmark/report/output/20191113_labelTransferEDA_F74_v2/"
ifelse(!dir.exists(outdir), dir.create(outdir), FALSE)
[1] FALSE
# model.cca <- readRDS("~/models/modelCCA_union_hvg_F74_SCElist_20191113.RDS")
# model.liger <- readRDS("~/models/modelLiger_union_hvg_F74_SCElist_20191113.RDS")
# model.conos <- readRDS("~/models/modelConos_union_hvg_F74_SCElist_20191113.RDS")
seu.cca <- readRDS("~/models/labelTransferCCA_union_hvg_F74_SCElist_20191119.RDS")
seu.liger <- readRDS("~/models/labelTransferLiger_union_hvg_F74_SCElist_20191119.RDS")
seu.conos <- readRDS("~/models/labelTransferConos_union_hvg_F74_SCElist_20191119.RDS")
integrate_features <- scan("~/intFeatures_union_hvg_2000_F74_SCElist_20191113.txt", what='')
Read 10603 items
int.list <- list(CCA=seu.cca, Liger=seu.liger, Conos=seu.conos)
# ## Make method color palette
# method.palette <- brewer_palette_4_values(names(int.list), "Set1")
Embeddings
Visualize label transfer on original ATAC data (embedded SnapATAC bins)


Filter low confidence calls
ggpubr::ggarrange(
plotlist = list(
DimPlot(atac.seu, reduction = "umap.snap", group.by = "predicted.id_CCA", cols=cell.type.pal, repel=TRUE) +
scale_color_manual(values = cell.type.pal, na.value="grey80") +
ggtitle("CCA"),
DimPlot(atac.seu, reduction = "umap.snap", group.by = "predicted.id_Liger", cols=cell.type.pal, repel=TRUE) +
scale_color_manual(values = cell.type.pal, na.value="grey80") + ggtitle("Liger"),
DimPlot(atac.seu, reduction = "umap.snap", group.by = "predicted.id_Conos", cols=cell.type.pal, repel=TRUE) +
scale_color_manual(values = cell.type.pal, na.value="grey80") + ggtitle("Conos")
),
common.legend = TRUE, ncol=3, nrow=1
) +
ggsave(paste0(outdir, "umap_labels_filtered.png"), width=16, height = 8)
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.


pl <- DimPlot(atac.seu, reduction = "umap.snap", group.by = "predicted.id_CCA", cols=cell.type.pal, label=TRUE, repel=TRUE) + ggtitle("CCA")
plotly::ggplotly(pl)
geom_GeomTextRepel() has yet to be implemented in plotly.
If you'd like to see this geom implemented,
Please open an issue with your example code at
https://github.com/ropensci/plotly/issues
pl <- DimPlot(atac.seu, reduction = "umap.snap", group.by = "predicted.id_Conos", cols=cell.type.pal, label=TRUE, repel=TRUE) + ggtitle("Conos")
plotly::ggplotly(pl)
geom_GeomTextRepel() has yet to be implemented in plotly.
If you'd like to see this geom implemented,
Please open an issue with your example code at
https://github.com/ropensci/plotly/issues
orig.RNA.seu <- as.Seurat(orig.RNA)
orig.RNA.seu <- FindVariableFeatures(orig.RNA.seu)
Calculating gene variances
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Calculating feature variances of standardized and clipped values
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
orig.RNA.seu <- ScaleData(orig.RNA.seu)
Centering and scaling data matrix
|
| | 0%
|
|==================================================== | 50%
|
|=======================================================================================================| 100%
orig.RNA.seu <- RunPCA(orig.RNA.seu)
PC_ 1
Positive: TRBC2, TRBC1, HMGA1, HIST1H1C, HIST1H3H, ITM2A, HIST1H2BJ, SMIM24, TRAV13-2, FXYD2
TRBV7-2, PCGF5, HIST1H2BH, IL32, HIST1H2BN, CHAC1, RASD1, TRBV27, TRAV13-1, TRAV8-2
TRAV8-4, PTPN6, SELL, HIST1H2BG, TAGAP, TRDC, TRAV38-2DV8, TRAV29DV5, TRBV9, TRAV41
Negative: CALD1, COL5A2, COL6A1, COL6A2, SPARC, THY1, DCN, COL3A1, NFIB, SPARCL1
TSHZ2, CPE, PLAC9, NID1, FKBP10, PTN, FLRT2, MAP1B, EFEMP2, BGN
CXCL12, RBP1, LAMB1, AHNAK, COL1A1, COL5A1, FSTL1, LUM, LAMA4, MDK
PC_ 2
Positive: SFRP1, NTRK2, PLAT, ISLR, NRK, SCARA5, ASPN, OSR1, OLFML3, MXRA8
CAPN6, PTPRD, PLP1, TMEFF2, CREB3L1, DKK3, CERCAM, MMP2, EBF2, SMOC2
CDO1, COL12A1, PDGFRA, LRRC17, THBS2, HTRA3, SFRP2, ANGPTL1, MAB21L1, MXRA5
Negative: MKI67, CDK1, NUSAP1, TOP2A, CCNA2, RRM2, UBE2C, BIRC5, KIFC1, TYMS
UBE2T, AURKB, CENPF, CENPM, CDCA8, TACC3, NCAPG, TPX2, ASF1B, CDKN3
GTSE1, CDCA3, HJURP, SPC25, MAD2L1, CDC20, PLK1, DLGAP5, NUF2, KIF22
PC_ 3
Positive: CCNA2, NUF2, GTSE1, CDCA8, UBE2T, AURKB, PBK, CDK1, NCAPG, NDC80
KIFC1, CDCA3, HJURP, MAD2L1, SPC25, PLK1, KIF15, DEPDC1B, BIRC5, CDCA5
CKS1B, RRM2, DLGAP5, HMMR, CENPA, KIF22, KIF20A, CDCA2, CENPF, KIF2C
Negative: HLA-DRB5, HLA-DRA, TYROBP, HLA-DRB1, HLA-DPA1, HLA-DPB1, HLA-DQA1, C1QC, C1QB, RNASE1
HLA-DQB1, A2M, C1QA, CSF1R, STAB1, HCK, HLA-DMB, FOLR2, SAMHD1, LYZ
MS4A7, SPI1, CD36, MS4A6A, CYBB, TMEM176B, CD74, IGSF6, MPEG1, MS4A4A
PC_ 4
Positive: GYPA, GYPB, NFE2, GYPE, ANK1, SLC4A1, RHAG, AHSP, DMTN, KLF1
GATA1, TMOD1, TMEM56, HBZ, HBG1, GMPR, C17orf99, SMIM5, HBQ1, TSPO2
ALAS2, PHOSPHO1, CR1L, TRIM58, HBM, EPB42, RHD, RHCE, SPTA1, SMIM1
Negative: TMSB10, TRBC2, CCL21, IL32, CXCL13, TRBC1, APLNR, CCL19, MIF, HMGB1
COX4I2, COL4A1, COL15A1, MADCAM1, FDCSP, CCL17, NTS, TNC, CDH5, FABP4
CAV1, COL4A2, CRIP2, RGS5, KLRB1, MYLK, IFITM1, IL33, PAPLN, HPN
PC_ 5
Positive: APLNR, CDH5, CAV1, COL15A1, CRIP2, COL4A1, CCL21, KDR, CLDN5, MADCAM1
FABP4, IL33, COL4A2, CXCL13, TM4SF1, PODXL, CCL19, COX4I2, ESAM, BCAM
NTS, PAPLN, SPNS2, MYLK, C8orf4, ADGRF5, TM4SF18, TGM2, RP11-536O18.1, CXorf36
Negative: CSF1R, MS4A4A, CYBB, PLD4, MS4A6A, CD163, HCK, IGSF6, FOLR2, ADAP2
CD86, MS4A7, MARCH1, MRC1, F13A1, MPEG1, CD14, SPI1, HLA-DMB, GPR34
CD33, TGFBI, CLEC7A, TIMD4, CEBPA, SIGLEC1, CSF2RA, SLC15A3, LY86, AGR2
orig.RNA.seu <- RunUMAP(orig.RNA.seu, dims=1:40)
10:43:01 UMAP embedding parameters a = 0.9922 b = 1.112
10:43:01 Read 8321 rows and found 40 numeric columns
10:43:01 Using Annoy for neighbor search, n_neighbors = 30
10:43:01 Building Annoy index with metric = cosine, n_trees = 50
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
10:43:03 Writing NN index file to temp file /tmp/RtmpsI55IG/file18a72231550b
10:43:03 Searching Annoy index using 1 thread, search_k = 3000
10:43:06 Annoy recall = 100%
10:43:08 Commencing smooth kNN distance calibration using 1 thread
10:43:10 Initializing from normalized Laplacian + noise
10:43:11 Commencing optimization for 500 epochs, with 367644 positive edges
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
10:43:32 Optimization finished
plotly::ggplotly(DimPlot(orig.RNA.seu, group.by="annotation"))
ggarrange(plotlist = map(cell.types[!cell.types %in% c( "NK","NA(1)","NA(3)","ILC3","SP (2)")] , ~ compareCluster(.x)), ncol=1) +
ggsave(paste0(outdir, "umap_clusters.png"), height = 30, width = 10)
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
Prediction score
Quantifies the uncertainty of the prediction. Calculated differently for every method, but used to define which cells are “unassigned”.
orig.composition <- orig.RNA$annotation
orig.frac <- table(orig.composition)/length(orig.composition)
orig.frac.df <- data.frame(orig.frac) %>%
dplyr::rename(predicted.id=orig.composition, frac.label=Freq) %>%
mutate(method="original.RNA")
score_cols <- str_subset(colnames(atac.seu@meta.data), 'score_')
label_cols <- str_subset(colnames(atac.seu@meta.data), 'predicted.id_')
pred.labels.df <- imap(list(CCA=pred.cca, Liger=pred.liger, Conos=pred.conos), ~
rownames_to_column(.x, "cell") %>%
rename_all(funs(str_remove(., str_c("_",.y)))) %>%
mutate(method=.y)
) %>%
purrr::reduce(bind_rows) %>%
mutate(score=ifelse(is.na(score), 0, score))
funs() is soft deprecated as of dplyr 0.8.0
Please use a list of either functions or lambdas:
# Simple named list:
list(mean = mean, median = median)
# Auto named with `tibble::lst()`:
tibble::lst(mean, median)
# Using lambdas
list(~ mean(., trim = .2), ~ median(., na.rm = TRUE))
[90mThis warning is displayed once per session.[39mbinding character and factor vector, coercing into character vector
predict_score_hist <-
pred.labels.df %>%
ggplot(aes(score, fill=method)) +
geom_histogram(position="identity", alpha=0.8, bins=40) +
facet_grid(method ~.) +
scale_fill_brewer(palette="Set1") +
xlab("Label prediction score") +
theme_bw(base_size = 16) +
theme(legend.position = "top")
cutoffs <- seq(0,1,0.05)
predict_score_cumedist <-
pred.labels.df %>%
group_by(method) %>%
mutate(bins=cut(score, breaks = cutoffs)) %>%
mutate(score=as.numeric(str_remove_all(as.character(bins), ".+,|]"))) %>%
ggplot(aes(score, color=method)) +
stat_ecdf(size=0.8, alpha=0.7) +
scale_color_brewer(palette = "Set1") +
ylab("Fraction of unassigned cells") +
xlab("Prediction score cutoff") +
theme_bw(base_size = 16) +
xlim(0,1) +
coord_fixed() +
guides(color="none")
ggpubr::ggarrange(predict_score_hist, predict_score_cumedist, common.legend = TRUE, widths = c(0.8, 1.2),
labels=c("A", "B")) +
ggsave(paste0(outdir, "prediction_score_distribution.png"), height = 6, width = 10)
Removed 47 rows containing non-finite values (stat_ecdf).


ggpubr::ggarrange(
plotlist = list(
FeaturePlot(atac.seu, reduction = "umap.snap", feature = "score_CCA" , coord.fixed = TRUE) + ggtitle("CCA"),
FeaturePlot(atac.seu, reduction = "umap.snap", feature = "score_Liger", coord.fixed = TRUE) + ggtitle("Liger"),
FeaturePlot(atac.seu, reduction = "umap.snap", feature = "score_Conos", coord.fixed = TRUE) + ggtitle("Conos")
),
common.legend = TRUE, ncol=3, nrow=1
) +
ggsave(paste0(outdir, "prediction_score_umaps.png"), height = 7, width=14)


Cell type composition
Compare cell type fractions (w uncertainty)
orig.rank.df <- orig.frac.df %>%
mutate(orig.rank=dense_rank(frac.label)) %>%
select(orig.rank, predicted.id) %>%
distinct() %>%
arrange(orig.rank) %>%
column_to_rownames("predicted.id")
pred.labels.df %>%
group_by(method) %>%
drop_na() %>%
mutate(tot.cells=n()) %>%
ungroup() %>%
group_by(method, predicted.id) %>%
summarise(tot.label = n(), tot.cells = max(tot.cells), mean.score=mean(score)) %>%
mutate(frac.label=tot.label/tot.cells) %>%
bind_rows(orig.frac.df) %>%
mutate(orig.rank = orig.rank.df[predicted.id,]) %>%
mutate(predicted.id=factor(predicted.id, levels=rownames(orig.rank.df)))%>%
# select(method, predicted.id, frac.label) %>%
# distinct() %>%
ggplot(aes(predicted.id, frac.label, fill=mean.score, color=mean.score)) +
geom_point(size=2) +
geom_col(width=0.05) +
coord_flip() +
# geom_line(aes(group=method)) +
facet_wrap(method~., nrow=1, ncol=4, scales="free_x") +
scale_color_viridis_c() +
scale_fill_viridis_c() +
ylab("Fraction of cells") +
theme_bw(base_size = 16) +
ggsave(paste0(outdir, "cell_type_composition_bars.png"), width = 15, height = 7)
binding character and factor vector, coercing into character vector

Agreement with unsupervised clustering of ATAC data
Calculate which fractions of NNs in bin based graph of ATAC cells have the same annotation
k = 30
atac.seu <- FindNeighbors(atac.seu, assay = "ATAC", reduction = "SnapATAC", dims = 1:15, k.param = k)
Computing nearest neighbor graph
Computing SNN
atac.nn.list <- getNNlist(atac.seu)
knn.score.CCA <- test.knn(atac.nn.list, setNames(pred.cca.filtered$predicted.id_CCA, rownames(pred.cca.filtered)))
p-value will be approximate in the presence of ties
knn.score.conos <- test.knn(atac.nn.list, setNames(pred.conos.filtered$predicted.id_Conos, rownames(pred.conos.filtered)))
p-value will be approximate in the presence of ties
knn.score.liger <- test.knn(atac.nn.list, setNames(pred.liger.filtered$predicted.id_Liger, rownames(pred.liger.filtered)))
p-value will be approximate in the presence of ties
knn_score_df <-
list(CCA=knn.score.CCA, conos=knn.score.conos, liger=knn.score.liger) %>%
imap( ~ data.frame(KNN_score = .x$KNN_score, D=.x$D, p.val=.x$p.val, method=.y)) %>%
# imap( ~ data.frame(KNN_score = .x$KNN_score, cell= names(.x$KNN_score), D=.x$D, p.val=.x$p.val, method=.y)) %>%
purrr::reduce(bind_rows) %>%
dplyr::mutate(KNN_score=ifelse(is.na(KNN_score), 0, KNN_score)) %>%
mutate(data="true")
Unequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vector
knn_score_null_df <-
list(CCA=knn.score.CCA, conos=knn.score.conos, liger=knn.score.liger) %>%
imap( ~ data.frame(KNN_score = .x$null, D=.x$D, p.val=.x$p.val, method=.y)) %>%
# imap( ~ data.frame(KNN_score = .x$KNN_score, cell= names(.x$KNN_score), D=.x$D, p.val=.x$p.val, method=.y)) %>%
purrr::reduce(bind_rows) %>%
dplyr::mutate(KNN_score=ifelse(is.na(KNN_score), 0, KNN_score)) %>%
mutate(data="null")
Unequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vector
bind_rows(knn_score_df, knn_score_null_df) %>%
ggplot(aes(KNN_score, color=method)) +
stat_ecdf( aes(alpha=data), size=1) +
# stat_ecdf(data=. %>% filter(data=="true"), size=1) +
facet_grid(method~.) +
scale_alpha_discrete( range=c(0.5,1), name="") +
scale_color_brewer(palette = "Set1") +
geom_text(data=. %>% distinct(method, D, p.val),
x=1, y=0.05, hjust=1,
aes(label=glue("KNN score = {round(D, 3)}, p.value: {p.val}"), y=c(0.90, 0.95, 1))) +
theme_bw(base_size = 16) +
ylab("ECDF") + xlab("Fraction of KNNs with shared label") +
ggsave(paste(outdir,"KNN_score_ecdf_unionHVG.png"), height = 6, width=7)
Using alpha for a discrete variable is not advised.

pred.labels[which(pred.labels == clust)]
AACAGTCTCCATATCT-1 AACCTTTGTGCGTTTA-1 AAGGAGCGTTCGGGAA-1 CAAAGCTCAGAGATGC-1 CATTCATCAAGGGTAC-1 CCTTGGTAGTCCCTCT-1 CTTGCTGCATTGCACA-1 GAAGTGGAGGCCTCGT-1
"DC" "DC" "DC" "DC" "DC" "DC" "DC" "DC"
GCACCTTAGCTGAGGT-1 GCTGTTCTCCCACTAC-1 TCTAGTTAGTAGGTCG-1 TTCATCACATAGCCAT-1 AAAGATGTCCACCTAC-1 AATGGCTCACAGGAAC-1 ACAGCGCCAGGTCCTG-1 ACATGCACACGTTGTA-1
"DC" "DC" "DC" "DC" "DC" "DC" "DC" "DC"
ACGTTAGCACATATCG-1 ATAGTCGCATTTGTTC-1 ATGGATCTCGATGTAC-1 CAACGTATCGCAAACT-1 CAGTATGAGCTGCCAC-1 CATGTTTGTCTCTGCT-1 CATTCATTCAGAATGA-1 CATTCCGCATCCCTTG-1
"DC" "DC" "DC" "DC" "DC" "DC" "DC" "DC"
CATTCCGTCCACTAGA-1 CCATACCAGGCTCAGA-1 CCTATTATCGCTGATA-1 CCTGGGACACCTGGTG-1 CCTTAATAGATTACGA-1 CGTGGCATCAATGCAC-1 CTAGCGGGTATTCGCA-1 CTAGGATAGCTTTCCC-1
"DC" "DC" "DC" "DC" "DC" "DC" "DC" "DC"
CTCTACGCACGTTACA-1 GAAACAAAGAATCAAC-1 GAAGAGCCACTTACAG-1 GAGATTCAGGCATGCA-1 GCACGCAGTTTGCCCT-1 GCAGATTAGACGTCAG-1 GCATTCCAGCGTCAAG-1 GGAGTAGAGCCTGGTC-1
"DC" "DC" "DC" "DC" "DC" "DC" "DC" "DC"
GGGTCTGCATACAACC-1 GGTACCGGTCCGCTTT-1 GTAATCGAGACTTGAA-1 GTCACAACATCGGCCA-1 GTGCACGCATAGAATG-1 GTTACGAAGAGGAACA-1 TAACGGTGTTTGATCG-1 TAACTTCAGCCTATAC-1
"DC" "DC" "DC" "DC" "DC" "DC" "DC" "DC"
TACATGGGTCACTCTC-1 TACATGGGTTTGTAGC-1 TCAATTCCAGGTAGCA-1 TCACAGAGTGCGTAGA-1 TCACCACGTAGGGTCA-1 TCGAGCGGTATTGTCG-1 TCGCAGGTCATCGCAA-1 TCTATTGAGGTTCGAG-1
"DC" "DC" "DC" "DC" "DC" "DC" "DC" "DC"
TGACTCCCAGCAAACG-1 TGATGCATCCTCATTA-1 TGCATTTAGTTTACGC-1 TGCTATTAGACCTATC-1 TGGCGCAGTTAGTAGA-1 TGGGTTAGTCTGGATT-1 TGTACAGAGCACCATT-1 TGTGTCCTCAAGAGAT-1
"DC" "DC" "DC" "DC" "DC" "DC" "DC" "DC"
TTACGTTGTGAGTCGA-1 TTCAACTAGTTACACC-1 TTCGATTTCCAGAGAG-1 TTGCGGGTCCATCATT-1 TTTGGTTAGGAAACTT-1
"DC" "DC" "DC" "DC" "DC"
Accessibility of markers
Taking markers from Fig. S2 of JP’s manuscript
thymus.markers <- c("PTPRC", "CD3G", "TYROBP","CD19","HOXA9",'FXYD2',"SH3TC1","CCR9","CD8A", "CD8B","PDCD1", "CRTAM","CD40LG","CCR6","FOXP3","SOX13","ZNF683","KLRD1","TNFSF11","VPREB1","MS4A1", "CLEC9A", "CLEC10A", "LAMP3", "IL3RA", "FCGR3B", "C2","TPSB2",
'ITGA2B',"GYPA", "CDH5", "RGS5","CDH1", "PDGFRA","CRABP1")
# pbmc.markers <- c("CD79A", "MS4A1", "CD8A", "CD8B", "LYZ")
# thymus.markers <- list(Fb=c("PDGFRA", "COLEC11", "FBN1", "PI16"),
# VSMC=c("PDGFRB", 'ACTA2', "RGS5"),
# Endo=c("PECAM1", "CDH5","LYVE1"),
# TEC = c("EPCAM", "FOXN1", "CCL25", "CCL19")
# )
thymus.markers.df <- imap(thymus.markers, ~ data.frame(gene=.x, cell.type.class=.y)) %>%
purrr::reduce(bind_rows)
marker.access.df <- atac.seu@assays$ACTIVITY@data[intersect(thymus.markers, rownames(atac.seu@assays$ACTIVITY)),] %>%
as.matrix() %>%
reshape2::melt(varnames=c("gene", "cell"), value.name="log.counts") %>%
full_join(rownames_to_column(atac.seu@meta.data[, label_cols], "cell")) %>%
# full_join(thymus.markers.df) %>%
pivot_longer(cols=label_cols, names_to = "method", values_to = "predicted.id") %>%
dplyr::mutate(method=str_remove(method,".+_")) %>%
filter(method %in% c("CCA", "Liger", "Conos"))
ordered_cell_types <- c("DN", "DP (Q)", "DP (P)", "SP", "NK", "ILC3", "DC", "Mac", "Ery", "Fib")
markers_pl <-
marker.access.df %>%
mutate(predicted.id = case_when(str_detect(predicted.id, "CD8") ~ "CD8+T",
# str_detect(predicted.id, "CD4") ~ "CD4+T",
TRUE ~ predicted.id
)
) %>%
mutate(predicted.id=factor(predicted.id, levels = ordered_cell_types)) %>%
group_by(method, predicted.id, gene) %>%
dplyr::mutate(frac.cells=sum(log.counts > 0)/n()) %>%
# filter(method=="CCA") %>%
ungroup() %>%
ggplot( aes( gene, predicted.id)) +
geom_point(aes(size=frac.cells, color=frac.cells)) +
facet_grid(method~., space="free", scales="free_x") +
scale_color_gradient(high="darkblue", low="white") +
# scale_color_viridis_c() +
theme_bw(base_size = 16) +
theme(axis.text.x = element_text(angle=90, hjust=1, vjust=0.5),
strip.text.x = element_text(angle=45))
markers_pl

ggsave(paste0(outdir, "Thymus_markers_accessibility.png"), height = 16, width = 12)
Reproducing Fig.2H on T-cell development
t.cell.markers <- list(known.markers = c("CD34", "IGLL1", "TRGC2", "TRDC", "PTCRA", "TRBC2", "TRAC", "CD4", "CD8A", "CD8B"),
chemokine.receptors = c("CCR9", "CCR7"),
tcr.activation = c("CD5", "CD27"),
proliferation=c("PCNA", "CDK1", "MKI67"),
cyclin.D = c("CCND2", "CCND3"),
recombination=c("RAG1", "RAG2"),
apoptosis=c("HRK","BMF", "TP53INP1"),
stage.markers = c("ST18", "HIVEP3", "RGPD3", "SMPD3", "AQP3", "RORC", "SATB1", "TOX2")
)
t.cell.markers.df <- imap(t.cell.markers, ~ data.frame(gene=.x, cell.type.class=.y)) %>%
purrr::reduce(bind_rows)
Unequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorUnequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vector
ordered.tcells <- c("DN", "DP (P)", "DP (Q)","SP (1)")
tcells.markers.df <-
atac.seu@assays$ACTIVITY@data[intersect(unlist(t.cell.markers), rownames(atac.seu@assays$ACTIVITY)),] %>%
as.matrix() %>%
reshape2::melt(varnames=c("gene", "cell"), value.name="log.counts") %>%
full_join(rownames_to_column(atac.seu@meta.data[, label_cols], "cell")) %>%
pivot_longer(cols=label_cols, names_to = "method", values_to = "predicted.id") %>%
dplyr::mutate(method=str_remove(method,".+_")) %>%
filter(method %in% c("CCA", "Liger", "Conos")) %>%
mutate(predicted.id=ifelse(str_detect(predicted.id, "CD8+"), "CD8+T", predicted.id)) %>%
mutate(predicted.id=ifelse(str_detect(predicted.id, "CD4+"), "CD4+T", predicted.id)) %>%
filter(predicted.id %in% ordered.tcells) %>%
group_by(method, predicted.id, gene) %>%
dplyr::mutate(frac.cells=sum(log.counts > 0)/n(), mean.acc=mean(log.counts)) %>%
ungroup()
Joining, by = "cell"
Column `cell` joining factor and character vector, coercing into character vector
tcells.markers.df %>%
full_join(t.cell.markers.df) %>%
# filter(method=="CCA") %>%
mutate(predicted.id=factor(predicted.id, levels=ordered.tcells)) %>%
ggplot(aes( predicted.id, gene)) +
facet_grid(cell.type.class~method, scales = "free_y", space="free") +
geom_point(aes(size=frac.cells, color=mean.acc)) +
scale_color_gradient(high="darkblue", low="white") +
# scale_color_gradient2(midpoint = 0.5) +
theme_bw(base_size = 16) +
theme(axis.text.x = element_text(angle=90, hjust=1, vjust=0.5),
strip.text.y = element_text(angle=0))
Joining, by = "gene"
Column `gene` joining factor and character vector, coercing into character vector

ggsave(paste0(outdir, "tcell_markers.png"), height = 14, width = 14)
Thoughts
- Conos scores a lot of cells with high confidence, but fails to assign cells to difficult clusters
- CCA resembles the composition of the RNA data better, but curious that the other methods identify way more
LS0tCnRpdGxlOiAiTGFiZWwgdHJhbnNmZXIgRURBIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgoKYGBge3J9CmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KFNuYXBBVEFDKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShEZXNjVG9vbHMpICAjIDQgQVVDIGZ1bmN0aW9uCmxpYnJhcnkoZ2x1ZSkKbGlicmFyeShnZ2FsbHV2aWFsKSAgIyA0IHJpdmVyIHBsb3QKbGlicmFyeShnZ3B1YnIpCnNvdXJjZSgifi9tdWx0aU9taWNfYmVuY2htYXJrL3V0aWxzLlIiKQpzb3VyY2UoIn4vbXVsdGlPbWljX2JlbmNobWFyay9LTk5fYWdyZWVtZW50LlIiKQoKCmdnX2NvbG9yX2h1ZSA8LSBmdW5jdGlvbihuKSB7CiAgaHVlcyA9IHNlcSgxNSwgMzc1LCBsZW5ndGggPSBuICsgMSkKICBoY2woaCA9IGh1ZXMsIGwgPSA2NSwgYyA9IDEwMClbMTpuXQp9CgojIyBNYWtlIG91dHB1dCBkaXJlY3RvcnkKb3V0ZGlyIDwtICJ+L211bHRpT21pY19iZW5jaG1hcmsvcmVwb3J0L291dHB1dC8yMDE5MTExM19sYWJlbFRyYW5zZmVyRURBX0Y3NF92Mi8iCmlmZWxzZSghZGlyLmV4aXN0cyhvdXRkaXIpLCBkaXIuY3JlYXRlKG91dGRpciksIEZBTFNFKQpgYGAKCgpgYGB7cn0KIyBtb2RlbC5jY2EgPC0gcmVhZFJEUygifi9tb2RlbHMvbW9kZWxDQ0FfdW5pb25faHZnX0Y3NF9TQ0VsaXN0XzIwMTkxMTEzLlJEUyIpCiMgbW9kZWwubGlnZXIgPC0gcmVhZFJEUygifi9tb2RlbHMvbW9kZWxMaWdlcl91bmlvbl9odmdfRjc0X1NDRWxpc3RfMjAxOTExMTMuUkRTIikKIyBtb2RlbC5jb25vcyA8LSByZWFkUkRTKCJ+L21vZGVscy9tb2RlbENvbm9zX3VuaW9uX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTExMy5SRFMiKQoKc2V1LmNjYSA8LSByZWFkUkRTKCJ+L21vZGVscy9sYWJlbFRyYW5zZmVyQ0NBX3VuaW9uX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTExOS5SRFMiKQpzZXUubGlnZXIgPC0gcmVhZFJEUygifi9tb2RlbHMvbGFiZWxUcmFuc2ZlckxpZ2VyX3VuaW9uX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTExOS5SRFMiKQpzZXUuY29ub3MgPC0gcmVhZFJEUygifi9tb2RlbHMvbGFiZWxUcmFuc2ZlckNvbm9zX3VuaW9uX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTExOS5SRFMiKQoKCmludGVncmF0ZV9mZWF0dXJlcyA8LSBzY2FuKCJ+L2ludEZlYXR1cmVzX3VuaW9uX2h2Z18yMDAwX0Y3NF9TQ0VsaXN0XzIwMTkxMTEzLnR4dCIsIHdoYXQ9JycpCgppbnQubGlzdCA8LSBsaXN0KENDQT1zZXUuY2NhLCBMaWdlcj1zZXUubGlnZXIsIENvbm9zPXNldS5jb25vcykKCiMgIyMgTWFrZSBtZXRob2QgY29sb3IgcGFsZXR0ZQojIG1ldGhvZC5wYWxldHRlIDwtIGJyZXdlcl9wYWxldHRlXzRfdmFsdWVzKG5hbWVzKGludC5saXN0KSwgIlNldDEiKQoKYGBgCgojIyMgRW1iZWRkaW5ncwpWaXN1YWxpemUgbGFiZWwgdHJhbnNmZXIgb24gb3JpZ2luYWwgQVRBQyBkYXRhIChlbWJlZGRlZCBTbmFwQVRBQyBiaW5zKQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KIyMgTG9hZCBvcmlnaW5hbCBkYXRhCm9yaWcuQVRBQyA8LSByZWFkUkRTKCJ+L215X2RhdGEvY2VsbHJhbmdlci1hdGFjMTEwX2NvdW50XzMwNDM5X1dTU1M4MDM4MzYwX0dSQ2gzOC0xXzFfMC5zbmFwQVRBQy5SRFMiKQpzY2UubGlzdCA8LSByZWFkUkRTKCJ+L215X2RhdGEvaW50ZWdyYXRlZF90aHltdXMvRjc0X1NDRWxpc3RfMjAxOTExMTkuUkRTIikKb3JpZy5STkEgPC0gc2NlLmxpc3QkUk5BCgojIyBNYWtlIFNldXJhdE9iamVjdHMKYXRhYy5zZXUgPC0gc25hcFRvU2V1cmF0KAogICAgb2JqPW9yaWcuQVRBQywgCiAgICBlaWdzLmRpbXM9MToyMCwgCiAgICBub3JtPVRSVUUsCiAgICBzY2FsZT1UUlVFCiAgICApCmF0YWMuc2V1IDwtIFJlbmFtZUNlbGxzKGF0YWMuc2V1LCBuZXcubmFtZXMgPSBvcmlnLkFUQUNAbWV0YURhdGEkYmFyY29kZSkKCiMjIEFkZCBjZWxsIHR5cGUgcHJlZGljdGlvbnMKZ2V0UHJlZGljdGVkTGFiZWxzIDwtIGZ1bmN0aW9uKHNldS5pbnQsIGludC5uYW1lLCBpZC5jb2w9InByZWRpY3RlZC5pZCIsIHNjb3JlLmNvbD0ic2NvcmUiLCBmaWx0ZXJfc2NvcmU9MCl7CiAgcHJlZC5kZiA8LSBzZXUuaW50JEFUQUNAbWV0YS5kYXRhWyxjKGlkLmNvbCwgc2NvcmUuY29sKSwgZHJvcD1GXSAKICBjb2xuYW1lcyhwcmVkLmRmKSA8LSBjKCdwcmVkaWN0ZWQuaWQnLCAic2NvcmUiKQogIHByZWQuZGYgPC0gcHJlZC5kZiAlPiUKICAgIHJvd25hbWVzX3RvX2NvbHVtbigiY2VsbCIpICU+JQogICAgbXV0YXRlKHByZWRpY3RlZC5pZCA9IGlmZWxzZShzY29yZSA8IGZpbHRlcl9zY29yZSwgTkEsIGFzLmNoYXJhY3RlcihwcmVkaWN0ZWQuaWQpKSkgJT4lCiAgICBjb2x1bW5fdG9fcm93bmFtZXMoImNlbGwiKQogIHJvd25hbWVzKHByZWQuZGYpIDwtIHN0cl9yZW1vdmUocm93bmFtZXMocHJlZC5kZiksICJeQVRBQ18iKQogIGNvbG5hbWVzKHByZWQuZGYpIDwtIGMoc3RyX2MoInByZWRpY3RlZC5pZCIsICJfIiwgaW50Lm5hbWUpLCBzdHJfYygic2NvcmUiLCAiXyIsIGludC5uYW1lKSkKICBwcmVkLmRmCiAgfQoKcHJlZC5jY2EgPC0gZ2V0UHJlZGljdGVkTGFiZWxzKHNldS5jY2EsICJDQ0EiLCBzY29yZS5jb2wgPSAicHJlZGljdGlvbi5zY29yZS5tYXgiKQpwcmVkLmxpZ2VyIDwtIGdldFByZWRpY3RlZExhYmVscyhzZXUubGlnZXIsICJMaWdlciIpCnByZWQuY29ub3MgPC0gZ2V0UHJlZGljdGVkTGFiZWxzKHNldS5jb25vcywgIkNvbm9zIikKCmlmIChhbGwocm93bmFtZXMocHJlZC5jb25vcykgPT0gcm93bmFtZXMocHJlZC5jY2EpKSAmIGFsbChyb3duYW1lcyhwcmVkLmNvbm9zKSA9PSByb3duYW1lcyhwcmVkLmxpZ2VyKSkpIHsKICBhdGFjLnNldSA8LSBBZGRNZXRhRGF0YShhdGFjLnNldSwgbWV0YWRhdGEgPSBjYmluZChwcmVkLmNjYSwgcHJlZC5saWdlciwgcHJlZC5jb25vcykpCn0gZWxzZSB7CiAgc3RvcCgiTm9uIGNvcnJlc3BvbmRpbmcgY2VsbCBuYW1lcyIpCn0KYGBgCgpgYGB7ciwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTh9CiMjIG1ha2UgY2VsbCB0eXBlIHBhbGV0dGUKY2VsbC50eXBlcyA8LSBsZXZlbHMoc2V1LmNjYSRSTkEkYW5ub3RhdGlvbikKY2VsbC50eXBlLnBhbCA8LSBicmV3ZXJfcGFsZXR0ZV80X3ZhbHVlcyhjZWxsLnR5cGVzLCBwYWxldHRlID0gIlNldDEiKSAlPiUgc2V0TmFtZXMoY2VsbC50eXBlcykKYXRhYy5zZXUgPC0gUnVuVU1BUChhdGFjLnNldSwgcmVkdWN0aW9uID0gIlNuYXBBVEFDIiwgcmVkdWN0aW9uLm5hbWUgPSAidW1hcC5zbmFwIiwgZGltcz0xOjIwKQoKZ2dwdWJyOjpnZ2FycmFuZ2UoCiAgcGxvdGxpc3QgPSBsaXN0KAogICAgRGltUGxvdChhdGFjLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5pZF9DQ0EiICAsIGNvbHM9Y2VsbC50eXBlLnBhbCwgbGFiZWw9VFJVRSwgcmVwZWw9VFJVRSkgKyBnZ3RpdGxlKCJDQ0EiKSwKICAgIERpbVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuaWRfTGlnZXIiLCBjb2xzPWNlbGwudHlwZS5wYWwsIGxhYmVsPVRSVUUsIHJlcGVsPVRSVUUpICsgZ2d0aXRsZSgiTGlnZXIiKSwKICAgIERpbVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuaWRfQ29ub3MiLCBjb2xzPWNlbGwudHlwZS5wYWwsIGxhYmVsPVRSVUUsIHJlcGVsPVRSVUUpICsgZ2d0aXRsZSgiQ29ub3MiKQogICksCiAgY29tbW9uLmxlZ2VuZCA9IFRSVUUsIG5jb2w9MywgbnJvdz0xCikgKwogIGdnc2F2ZShwYXN0ZTAob3V0ZGlyLCAidW1hcF9sYWJlbHMucG5nIiksIHdpZHRoPTE2LCBoZWlnaHQgPSA4KQoKCmBgYAoKRmlsdGVyIGxvdyBjb25maWRlbmNlIGNhbGxzIApgYGB7ciwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTh9CnByZWQuY2NhLmZpbHRlcmVkIDwtIGdldFByZWRpY3RlZExhYmVscyhzZXUuY2NhLCAiQ0NBIiwgc2NvcmUuY29sID0gInByZWRpY3Rpb24uc2NvcmUubWF4IiwgZmlsdGVyX3Njb3JlID0gMC41KQpwcmVkLmxpZ2VyLmZpbHRlcmVkIDwtIGdldFByZWRpY3RlZExhYmVscyhzZXUubGlnZXIsICJMaWdlciIsIGZpbHRlcl9zY29yZSA9IDAuNSkKcHJlZC5jb25vcy5maWx0ZXJlZCA8LSBnZXRQcmVkaWN0ZWRMYWJlbHMoc2V1LmNvbm9zLCAiQ29ub3MiLCBmaWx0ZXJfc2NvcmUgPSAwLjUpCgppZiAoYWxsKHJvd25hbWVzKHByZWQuY29ub3MpID09IHJvd25hbWVzKHByZWQuY2NhKSkgJiBhbGwocm93bmFtZXMocHJlZC5jb25vcykgPT0gcm93bmFtZXMocHJlZC5saWdlcikpKSB7CiAgYXRhYy5zZXUgPC0gQWRkTWV0YURhdGEoYXRhYy5zZXUsIG1ldGFkYXRhID0gY2JpbmQocHJlZC5jY2EuZmlsdGVyZWQsIHByZWQubGlnZXIuZmlsdGVyZWQsIHByZWQuY29ub3MuZmlsdGVyZWQpKQp9IGVsc2UgewogIHN0b3AoIk5vbiBjb3JyZXNwb25kaW5nIGNlbGwgbmFtZXMiKQp9CgpnZ3B1YnI6OmdnYXJyYW5nZSgKICBwbG90bGlzdCA9IGxpc3QoCiAgICBEaW1QbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkX0NDQSIsIGNvbHM9Y2VsbC50eXBlLnBhbCwgcmVwZWw9VFJVRSkgKyAKICAgICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGNlbGwudHlwZS5wYWwsIG5hLnZhbHVlPSJncmV5ODAiKSArCiAgICAgIGdndGl0bGUoIkNDQSIpLAogICAgRGltUGxvdChhdGFjLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5pZF9MaWdlciIsIGNvbHM9Y2VsbC50eXBlLnBhbCwgcmVwZWw9VFJVRSkgKyAKICAgICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGNlbGwudHlwZS5wYWwsIG5hLnZhbHVlPSJncmV5ODAiKSArIGdndGl0bGUoIkxpZ2VyIiksCiAgICBEaW1QbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkX0Nvbm9zIiwgY29scz1jZWxsLnR5cGUucGFsLCByZXBlbD1UUlVFKSArIAogICAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY2VsbC50eXBlLnBhbCwgbmEudmFsdWU9ImdyZXk4MCIpICsgZ2d0aXRsZSgiQ29ub3MiKQogICksCiAgY29tbW9uLmxlZ2VuZCA9IFRSVUUsIG5jb2w9MywgbnJvdz0xCikgKwogIGdnc2F2ZShwYXN0ZTAob3V0ZGlyLCAidW1hcF9sYWJlbHNfZmlsdGVyZWQucG5nIiksIHdpZHRoPTE2LCBoZWlnaHQgPSA4KQoKYGBgCgoKYGBge3J9CnBsIDwtICAgICBEaW1QbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkX0NDQSIsIGNvbHM9Y2VsbC50eXBlLnBhbCwgbGFiZWw9VFJVRSwgcmVwZWw9VFJVRSkgKyBnZ3RpdGxlKCJDQ0EiKQpwbG90bHk6OmdncGxvdGx5KHBsKQpgYGAKCmBgYHtyfQpwbCA8LSAgICAgRGltUGxvdChhdGFjLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5pZF9Db25vcyIsIGNvbHM9Y2VsbC50eXBlLnBhbCwgbGFiZWw9VFJVRSwgcmVwZWw9VFJVRSkgKyBnZ3RpdGxlKCJDb25vcyIpCnBsb3RseTo6Z2dwbG90bHkocGwpCmBgYAoKYGBge3J9Cm9yaWcuUk5BLnNldSA8LSBhcy5TZXVyYXQob3JpZy5STkEpCm9yaWcuUk5BLnNldSA8LSBGaW5kVmFyaWFibGVGZWF0dXJlcyhvcmlnLlJOQS5zZXUpCm9yaWcuUk5BLnNldSA8LSBTY2FsZURhdGEob3JpZy5STkEuc2V1KQpvcmlnLlJOQS5zZXUgPC0gUnVuUENBKG9yaWcuUk5BLnNldSkKb3JpZy5STkEuc2V1IDwtIFJ1blVNQVAob3JpZy5STkEuc2V1LCBkaW1zPTE6NDApCgpwbG90bHk6OmdncGxvdGx5KERpbVBsb3Qob3JpZy5STkEuc2V1LCBncm91cC5ieT0iYW5ub3RhdGlvbiIpKQpgYGAKYGBge3IsIGZpZy53aWR0aD0xNSwgZmlnLmhlaWdodD0yMH0KY29tcGFyZUNsdXN0ZXIgPC0gZnVuY3Rpb24oY2x1c3QpewogIHBsb3RsaXN0IDwtIG1hcChsaXN0KCJDQ0EiLCAiTGlnZXIiLCAiQ29ub3MiKSwgCiAgICAgICAgICAgICAgICAgIH4gRmVhdHVyZVBsb3RDbHVzdGVyKGF0YWMuc2V1LCBhbm5vdGF0aW9uX2NvbCA9IGdsdWUoJ3ByZWRpY3RlZC5pZF97Lnh9JyksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZlYXR1cmVfY29sPWdsdWUoInNjb3JlX3sueH0iKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbHVzdGVyPWNsdXN0LCBsYWJlbD1nbHVlKCd7Y2x1c3R9IC0gey54fScpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgICAgKQogIHJuYV9wbG90IDwtIERpbVBsb3RDbHVzdGVyKG9yaWcuUk5BLnNldSwgYW5ub3RhdGlvbl9jb2wgPSAiYW5ub3RhdGlvbiIsIGNsdXN0ZXIgPSBjbHVzdCwgbGFiZWw9Z2x1ZSgie2NsdXN0fSAtIFJOQSIpLCByZWR1Y3QgPSAidW1hcCIpCiAgcGxvdGxpc3RbWzRdXSA8LSBybmFfcGxvdAogIGdnYXJyYW5nZShwbG90bGlzdCA9IHBsb3RsaXN0LCBucm93PTEpCiAgfQoKZ2dhcnJhbmdlKHBsb3RsaXN0ID0gbWFwKGNlbGwudHlwZXNbIWNlbGwudHlwZXMgJWluJSBjKCAiTksiLCJOQSgxKSIsIk5BKDMpIiwiSUxDMyIsIlNQICgyKSIpXSAsIH4gY29tcGFyZUNsdXN0ZXIoLngpKSwgbmNvbD0xKSArCiAgZ2dzYXZlKHBhc3RlMChvdXRkaXIsICJ1bWFwX2NsdXN0ZXJzLnBuZyIpLCBoZWlnaHQgPSAzMCwgd2lkdGggPSAxMCkKYGBgCgojIyBQcmVkaWN0aW9uIHNjb3JlClF1YW50aWZpZXMgdGhlIHVuY2VydGFpbnR5IG9mIHRoZSBwcmVkaWN0aW9uLiBDYWxjdWxhdGVkIGRpZmZlcmVudGx5IGZvciBldmVyeSBtZXRob2QsIGJ1dCB1c2VkIHRvIGRlZmluZSB3aGljaCBjZWxscyBhcmUgInVuYXNzaWduZWQiLgoKCmBgYHtyfQpvcmlnLmNvbXBvc2l0aW9uIDwtIG9yaWcuUk5BJGFubm90YXRpb24Kb3JpZy5mcmFjIDwtIHRhYmxlKG9yaWcuY29tcG9zaXRpb24pL2xlbmd0aChvcmlnLmNvbXBvc2l0aW9uKQoKb3JpZy5mcmFjLmRmIDwtIGRhdGEuZnJhbWUob3JpZy5mcmFjKSAlPiUKICBkcGx5cjo6cmVuYW1lKHByZWRpY3RlZC5pZD1vcmlnLmNvbXBvc2l0aW9uLCBmcmFjLmxhYmVsPUZyZXEpICU+JQogIG11dGF0ZShtZXRob2Q9Im9yaWdpbmFsLlJOQSIpCgpzY29yZV9jb2xzIDwtIHN0cl9zdWJzZXQoY29sbmFtZXMoYXRhYy5zZXVAbWV0YS5kYXRhKSwgJ3Njb3JlXycpCmxhYmVsX2NvbHMgPC0gc3RyX3N1YnNldChjb2xuYW1lcyhhdGFjLnNldUBtZXRhLmRhdGEpLCAncHJlZGljdGVkLmlkXycpCgpwcmVkLmxhYmVscy5kZiA8LSBpbWFwKGxpc3QoQ0NBPXByZWQuY2NhLCBMaWdlcj1wcmVkLmxpZ2VyLCBDb25vcz1wcmVkLmNvbm9zKSwgfiAKICAgICAgcm93bmFtZXNfdG9fY29sdW1uKC54LCAiY2VsbCIpICU+JQogICAgICByZW5hbWVfYWxsKGZ1bnMoc3RyX3JlbW92ZSguLCBzdHJfYygiXyIsLnkpKSkpICU+JQogICAgICBtdXRhdGUobWV0aG9kPS55KQogICAgKSAlPiUKICBwdXJycjo6cmVkdWNlKGJpbmRfcm93cykgJT4lCiAgbXV0YXRlKHNjb3JlPWlmZWxzZShpcy5uYShzY29yZSksIDAsIHNjb3JlKSkKCnByZWRpY3Rfc2NvcmVfaGlzdCA8LSAKICBwcmVkLmxhYmVscy5kZiAlPiUKICBnZ3Bsb3QoYWVzKHNjb3JlLCBmaWxsPW1ldGhvZCkpICsKICBnZW9tX2hpc3RvZ3JhbShwb3NpdGlvbj0iaWRlbnRpdHkiLCBhbHBoYT0wLjgsIGJpbnM9NDApICsKICBmYWNldF9ncmlkKG1ldGhvZCB+LikgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGU9IlNldDEiKSArCiAgeGxhYigiTGFiZWwgcHJlZGljdGlvbiBzY29yZSIpICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQoKY3V0b2ZmcyA8LSBzZXEoMCwxLDAuMDUpCnByZWRpY3Rfc2NvcmVfY3VtZWRpc3QgPC0KICBwcmVkLmxhYmVscy5kZiAlPiUKICBncm91cF9ieShtZXRob2QpICU+JQogIG11dGF0ZShiaW5zPWN1dChzY29yZSwgYnJlYWtzID0gY3V0b2ZmcykpICU+JQogIG11dGF0ZShzY29yZT1hcy5udW1lcmljKHN0cl9yZW1vdmVfYWxsKGFzLmNoYXJhY3RlcihiaW5zKSwgIi4rLHxdIikpKSAlPiUKICBnZ3Bsb3QoYWVzKHNjb3JlLCBjb2xvcj1tZXRob2QpKSArCiAgc3RhdF9lY2RmKHNpemU9MC44LCBhbHBoYT0wLjcpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKwogIHlsYWIoIkZyYWN0aW9uIG9mIHVuYXNzaWduZWQgY2VsbHMiKSArCiAgeGxhYigiUHJlZGljdGlvbiBzY29yZSBjdXRvZmYiKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpICsKICB4bGltKDAsMSkgKwogIGNvb3JkX2ZpeGVkKCkgKwogIGd1aWRlcyhjb2xvcj0ibm9uZSIpIAoKZ2dwdWJyOjpnZ2FycmFuZ2UocHJlZGljdF9zY29yZV9oaXN0LCBwcmVkaWN0X3Njb3JlX2N1bWVkaXN0LCBjb21tb24ubGVnZW5kID0gVFJVRSwgd2lkdGhzID0gYygwLjgsIDEuMiksCiAgICAgICAgICBsYWJlbHM9YygiQSIsICJCIikpICsKICBnZ3NhdmUocGFzdGUwKG91dGRpciwgInByZWRpY3Rpb25fc2NvcmVfZGlzdHJpYnV0aW9uLnBuZyIpLCBoZWlnaHQgPSA2LCB3aWR0aCA9IDEwKQpgYGAKCmBgYHtyLCBmaWcud2lkdGg9MTYsIGZpZy5oZWlnaHQ9OH0KZ2dwdWJyOjpnZ2FycmFuZ2UoCiAgcGxvdGxpc3QgPSBsaXN0KAogICAgRmVhdHVyZVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBmZWF0dXJlID0gInNjb3JlX0NDQSIgICwgY29vcmQuZml4ZWQgPSBUUlVFKSArIGdndGl0bGUoIkNDQSIpLAogICAgRmVhdHVyZVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBmZWF0dXJlID0gInNjb3JlX0xpZ2VyIiwgY29vcmQuZml4ZWQgPSBUUlVFKSArIGdndGl0bGUoIkxpZ2VyIiksCiAgICBGZWF0dXJlUGxvdChhdGFjLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGZlYXR1cmUgPSAic2NvcmVfQ29ub3MiLCBjb29yZC5maXhlZCA9IFRSVUUpICsgZ2d0aXRsZSgiQ29ub3MiKQogICksCiAgY29tbW9uLmxlZ2VuZCA9IFRSVUUsIG5jb2w9MywgbnJvdz0xCikgKwogIGdnc2F2ZShwYXN0ZTAob3V0ZGlyLCAicHJlZGljdGlvbl9zY29yZV91bWFwcy5wbmciKSwgaGVpZ2h0ID0gNywgd2lkdGg9MTQpCmBgYAoKCgojIyBDZWxsIHR5cGUgY29tcG9zaXRpb24KCkNvbXBhcmUgY2VsbCB0eXBlIGZyYWN0aW9ucyAodyB1bmNlcnRhaW50eSkKCmBgYHtyLCBmaWcud2lkdGg9MTQsIGZpZy5oZWlnaHQ9N30Kb3JpZy5yYW5rLmRmIDwtIG9yaWcuZnJhYy5kZiAlPiUgCiAgbXV0YXRlKG9yaWcucmFuaz1kZW5zZV9yYW5rKGZyYWMubGFiZWwpKSAlPiUKICBzZWxlY3Qob3JpZy5yYW5rLCBwcmVkaWN0ZWQuaWQpICU+JQogIGRpc3RpbmN0KCkgJT4lCiAgYXJyYW5nZShvcmlnLnJhbmspICU+JQogIGNvbHVtbl90b19yb3duYW1lcygicHJlZGljdGVkLmlkIikgCgpwcmVkLmxhYmVscy5kZiAlPiUKICBncm91cF9ieShtZXRob2QpICU+JQogIGRyb3BfbmEoKSAlPiUKICBtdXRhdGUodG90LmNlbGxzPW4oKSkgJT4lCiAgdW5ncm91cCgpICU+JQogIGdyb3VwX2J5KG1ldGhvZCwgcHJlZGljdGVkLmlkKSAlPiUKICBzdW1tYXJpc2UodG90LmxhYmVsID0gbigpLCB0b3QuY2VsbHMgPSBtYXgodG90LmNlbGxzKSwgbWVhbi5zY29yZT1tZWFuKHNjb3JlKSkgJT4lCiAgbXV0YXRlKGZyYWMubGFiZWw9dG90LmxhYmVsL3RvdC5jZWxscykgJT4lCiAgYmluZF9yb3dzKG9yaWcuZnJhYy5kZikgJT4lCiAgbXV0YXRlKG9yaWcucmFuayA9IG9yaWcucmFuay5kZltwcmVkaWN0ZWQuaWQsXSkgJT4lCiAgbXV0YXRlKHByZWRpY3RlZC5pZD1mYWN0b3IocHJlZGljdGVkLmlkLCBsZXZlbHM9cm93bmFtZXMob3JpZy5yYW5rLmRmKSkpJT4lCiAgIyBzZWxlY3QobWV0aG9kLCBwcmVkaWN0ZWQuaWQsIGZyYWMubGFiZWwpICU+JQogICMgZGlzdGluY3QoKSAlPiUKICBnZ3Bsb3QoYWVzKHByZWRpY3RlZC5pZCwgZnJhYy5sYWJlbCwgZmlsbD1tZWFuLnNjb3JlLCBjb2xvcj1tZWFuLnNjb3JlKSkgKwogIGdlb21fcG9pbnQoc2l6ZT0yKSArCiAgZ2VvbV9jb2wod2lkdGg9MC4wNSkgKwogIGNvb3JkX2ZsaXAoKSArCiAgIyBnZW9tX2xpbmUoYWVzKGdyb3VwPW1ldGhvZCkpICsKICBmYWNldF93cmFwKG1ldGhvZH4uLCBucm93PTEsIG5jb2w9NCwgc2NhbGVzPSJmcmVlX3giKSArCiAgc2NhbGVfY29sb3JfdmlyaWRpc19jKCkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCkgKwogIHlsYWIoIkZyYWN0aW9uIG9mIGNlbGxzIikgKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE2KSArCiAgZ2dzYXZlKHBhc3RlMChvdXRkaXIsICJjZWxsX3R5cGVfY29tcG9zaXRpb25fYmFycy5wbmciKSwgd2lkdGggPSAxNSwgaGVpZ2h0ID0gNykKYGBgCgoKPCEtLSBEb2VzIHRoZSB1bmNlcnRhaW50eSBkZXBlbmQgb24gdGhlIHNpemUgb2YgdGhlIGNsdXN0ZXI/IC0tPgo8IS0tIGBgYHtyLCBmaWcud2lkdGg9MTQsIGZpZy5oZWlnaHQ9NX0gLS0+Cgo8IS0tIHByZWQubGFiZWxzLmRmICU+JSAtLT4KPCEtLSAgIGdyb3VwX2J5KG1ldGhvZCkgJT4lIC0tPgo8IS0tICAgZHJvcF9uYSgpICU+JSAtLT4KPCEtLSAgIG11dGF0ZSh0b3QuY2VsbHM9bigpKSAlPiUgLS0+CjwhLS0gICB1bmdyb3VwKCkgJT4lIC0tPgo8IS0tICAgZ3JvdXBfYnkobWV0aG9kLCBwcmVkaWN0ZWQuaWQpICU+JSAtLT4KPCEtLSAgIHN1bW1hcmlzZSh0b3QubGFiZWwgPSBuKCksIHRvdC5jZWxscyA9IG1heCh0b3QuY2VsbHMpLCBtZWFuLnNjb3JlPW1lZGlhbihzY29yZSksIHNkLnNjb3JlPW1hZChzY29yZSkpICU+JSAtLT4KPCEtLSAgIG11dGF0ZShmcmFjLmxhYmVsPXRvdC5sYWJlbC90b3QuY2VsbHMpICU+JSAtLT4KPCEtLSAgICMgYmluZF9yb3dzKG9yaWcuZnJhYy5kZikgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyhmcmFjLmxhYmVsLCBtZWFuLnNjb3JlLCBjb2xvcj1tZXRob2QpKSArIC0tPgo8IS0tICAgZ2VvbV9wb2ludChzaXplPTIpICsgLS0+CjwhLS0gICBnZW9tX2Vycm9yYmFyKGFlcyh5bWluPW1lYW4uc2NvcmUtc2Quc2NvcmUsIHltYXg9bWVhbi5zY29yZStzZC5zY29yZSksIGFscGhhPTAuNikgKyAtLT4KPCEtLSAgIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlPSJTZXQxIikgKyAtLT4KPCEtLSAgICMgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxvZXNzIiwgc3Bhbj0xLjIpICsgLS0+CjwhLS0gICBmYWNldF9ncmlkKC4gfiBtZXRob2QpICsgLS0+CjwhLS0gICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgKyAtLT4KPCEtLSAgIHN0YXRfY29yKGxhYmVsLnggPSAwLjIsIGxhYmVsLnk9MC4yNSwgY29sb3I9ImJsYWNrIiwgc2l6ZT01KSAgLS0+CgoKPCEtLSBgYGAgLS0+CgojIyMgQWdyZWVtZW50IHdpdGggdW5zdXBlcnZpc2VkIGNsdXN0ZXJpbmcgb2YgQVRBQyBkYXRhCkNhbGN1bGF0ZSB3aGljaCBmcmFjdGlvbnMgb2YgTk5zIGluIGJpbiBiYXNlZCBncmFwaCBvZiBBVEFDIGNlbGxzIGhhdmUgdGhlIHNhbWUgYW5ub3RhdGlvbgpgYGB7cn0KayA9IDMwCmF0YWMuc2V1IDwtIEZpbmROZWlnaGJvcnMoYXRhYy5zZXUsIGFzc2F5ID0gIkFUQUMiLCByZWR1Y3Rpb24gPSAiU25hcEFUQUMiLCBkaW1zID0gMToxNSwgay5wYXJhbSA9IGspCgphdGFjLm5uLmxpc3QgPC0gZ2V0Tk5saXN0KGF0YWMuc2V1KQoKa25uLnNjb3JlLkNDQSA8LSB0ZXN0LmtubihhdGFjLm5uLmxpc3QsIHNldE5hbWVzKHByZWQuY2NhLmZpbHRlcmVkJHByZWRpY3RlZC5pZF9DQ0EsIHJvd25hbWVzKHByZWQuY2NhLmZpbHRlcmVkKSkpCmtubi5zY29yZS5jb25vcyA8LSB0ZXN0LmtubihhdGFjLm5uLmxpc3QsIHNldE5hbWVzKHByZWQuY29ub3MuZmlsdGVyZWQkcHJlZGljdGVkLmlkX0Nvbm9zLCByb3duYW1lcyhwcmVkLmNvbm9zLmZpbHRlcmVkKSkpCmtubi5zY29yZS5saWdlciA8LSB0ZXN0LmtubihhdGFjLm5uLmxpc3QsIHNldE5hbWVzKHByZWQubGlnZXIuZmlsdGVyZWQkcHJlZGljdGVkLmlkX0xpZ2VyLCByb3duYW1lcyhwcmVkLmxpZ2VyLmZpbHRlcmVkKSkpCgprbm5fc2NvcmVfZGYgPC0KICBsaXN0KENDQT1rbm4uc2NvcmUuQ0NBLCBjb25vcz1rbm4uc2NvcmUuY29ub3MsIGxpZ2VyPWtubi5zY29yZS5saWdlcikgJT4lCiAgaW1hcCggfiBkYXRhLmZyYW1lKEtOTl9zY29yZSA9IC54JEtOTl9zY29yZSwgRD0ueCRELCBwLnZhbD0ueCRwLnZhbCwgbWV0aG9kPS55KSkgJT4lCiAgIyBpbWFwKCB+IGRhdGEuZnJhbWUoS05OX3Njb3JlID0gLngkS05OX3Njb3JlLCBjZWxsPSBuYW1lcygueCRLTk5fc2NvcmUpLCBEPS54JEQsIHAudmFsPS54JHAudmFsLCBtZXRob2Q9LnkpKSAlPiUKICBwdXJycjo6cmVkdWNlKGJpbmRfcm93cykgJT4lCiAgZHBseXI6Om11dGF0ZShLTk5fc2NvcmU9aWZlbHNlKGlzLm5hKEtOTl9zY29yZSksIDAsIEtOTl9zY29yZSkpICU+JQogIG11dGF0ZShkYXRhPSJ0cnVlIikKa25uX3Njb3JlX251bGxfZGYgPC0KICBsaXN0KENDQT1rbm4uc2NvcmUuQ0NBLCBjb25vcz1rbm4uc2NvcmUuY29ub3MsIGxpZ2VyPWtubi5zY29yZS5saWdlcikgJT4lCiAgaW1hcCggfiBkYXRhLmZyYW1lKEtOTl9zY29yZSA9IC54JG51bGwsIEQ9LngkRCwgcC52YWw9LngkcC52YWwsIG1ldGhvZD0ueSkpICU+JQogICMgaW1hcCggfiBkYXRhLmZyYW1lKEtOTl9zY29yZSA9IC54JEtOTl9zY29yZSwgY2VsbD0gbmFtZXMoLngkS05OX3Njb3JlKSwgRD0ueCRELCBwLnZhbD0ueCRwLnZhbCwgbWV0aG9kPS55KSkgJT4lCiAgcHVycnI6OnJlZHVjZShiaW5kX3Jvd3MpICU+JQogIGRwbHlyOjptdXRhdGUoS05OX3Njb3JlPWlmZWxzZShpcy5uYShLTk5fc2NvcmUpLCAwLCBLTk5fc2NvcmUpKSAlPiUKICBtdXRhdGUoZGF0YT0ibnVsbCIpCgoKYmluZF9yb3dzKGtubl9zY29yZV9kZiwga25uX3Njb3JlX251bGxfZGYpICU+JQogIGdncGxvdChhZXMoS05OX3Njb3JlLCBjb2xvcj1tZXRob2QpKSArCiAgc3RhdF9lY2RmKCBhZXMoYWxwaGE9ZGF0YSksIHNpemU9MSkgKwogICMgc3RhdF9lY2RmKGRhdGE9LiAlPiUgZmlsdGVyKGRhdGE9PSJ0cnVlIiksIHNpemU9MSkgKwogIGZhY2V0X2dyaWQobWV0aG9kfi4pICsKICBzY2FsZV9hbHBoYV9kaXNjcmV0ZSggcmFuZ2U9YygwLjUsMSksIG5hbWU9IiIpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKwogIGdlb21fdGV4dChkYXRhPS4gJT4lIGRpc3RpbmN0KG1ldGhvZCwgRCwgcC52YWwpLCAKICAgICAgICAgICAgeD0xLCB5PTAuMDUsIGhqdXN0PTEsCiAgICAgICAgICAgIGFlcyhsYWJlbD1nbHVlKCJLTk4gc2NvcmUgPSB7cm91bmQoRCwgMyl9LCBwLnZhbHVlOiB7cC52YWx9IiksIHk9YygwLjkwLCAwLjk1LCAxKSkpICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgKwogIHlsYWIoIkVDREYiKSArIHhsYWIoIkZyYWN0aW9uIG9mIEtOTnMgd2l0aCBzaGFyZWQgbGFiZWwiKSArCiAgZ2dzYXZlKHBhc3RlKG91dGRpciwiS05OX3Njb3JlX2VjZGZfdW5pb25IVkcucG5nIiksIGhlaWdodCA9IDYsIHdpZHRoPTcpCmBgYApgYGB7cn0KY2x1c3QgPSAiREMiCnByZWQubGFiZWxzIDwtIHNldE5hbWVzKHByZWQuY2NhLmZpbHRlcmVkJHByZWRpY3RlZC5pZF9DQ0EsIHJvd25hbWVzKHByZWQuY2NhLmZpbHRlcmVkKSkgCnByZWQubGFiZWxzW3doaWNoKHByZWQubGFiZWxzID09IGNsdXN0KV0KS05OX3Njb3JlKGF0YWMubm4ubGlzdCwgKQpgYGAKCgo8IS0tICMjIyMgV2hpY2ggY2VsbHMgYXJlIGluY29uc2lzdGVudGx5IGFsaWduZWQ/IC0tPgo8IS0tIGBgYHtyLCBmaWcud2lkdGg9MTQsIGZpZy5oZWlnaHQ9MTB9IC0tPgo8IS0tIHByZWQubGFiZWxzLmRmICU+JSAtLT4KPCEtLSAgIHNlbGVjdChtZXRob2QsIHByZWRpY3RlZC5pZCwgY2VsbCkgJT4lIC0tPgo8IS0tICAgbXV0YXRlKHByZWRpY3RlZC5pZD1pZmVsc2UoaXMubmEocHJlZGljdGVkLmlkKSwgIm5vbmUiLCBwcmVkaWN0ZWQuaWQpKSAlPiUgLS0+CjwhLS0gICBnZ3Bsb3QoYWVzKHg9bWV0aG9kLCBzdHJhdHVtPXByZWRpY3RlZC5pZCwgYWxsdXZpdW09Y2VsbCwgZmlsbD1wcmVkaWN0ZWQuaWQsIGxhYmVsPXByZWRpY3RlZC5pZCkpICsgLS0+CjwhLS0gICBnZW9tX2Zsb3coKSArIC0tPgo8IS0tICAgZ2VvbV9zdHJhdHVtKGNvbG9yPU5BKSArIC0tPgo8IS0tICAgZ2VvbV90ZXh0KHN0YXQ9InN0cmF0dW0iKSArIC0tPgo8IS0tICAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpIC0tPgo8IS0tIGBgYCAtLT4KPCEtLSAjIyMjIFdoaWNoIGNlbGxzIGFyZSBpbmNvbnNpc3RlbnRseSBzY29yZWQ/IC0tPgo8IS0tIGBgYHtyLCBmaWcud2lkdGg9MTQsIGZpZy5oZWlnaHQ9OH0gLS0+CjwhLS0gbGlicmFyeShnZ2FsbHV2aWFsKSAtLT4KPCEtLSBwcmVkLmxhYmVscy5kZiAlPiUgLS0+CjwhLS0gICBzZWxlY3QobWV0aG9kLCBwcmVkaWN0ZWQuaWQsIGNlbGwpICU+JSAtLT4KPCEtLSAgIG11dGF0ZShwcmVkaWN0ZWQuaWQ9aWZlbHNlKGlzLm5hKHByZWRpY3RlZC5pZCksICJub25lIiwgcHJlZGljdGVkLmlkKSkgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyh4PW1ldGhvZCwgc3RyYXR1bT1wcmVkaWN0ZWQuaWQsIGFsbHV2aXVtPWNlbGwsIGZpbGw9cHJlZGljdGVkLmlkLCBsYWJlbD1wcmVkaWN0ZWQuaWQpKSArIC0tPgo8IS0tICAgZ2VvbV9mbG93KCkgKyAtLT4KPCEtLSAgIGdlb21fc3RyYXR1bSgpICsgLS0+CjwhLS0gICBnZW9tX3RleHQoc3RhdD0ic3RyYXR1bSIpICsgLS0+CjwhLS0gICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNikgLS0+CjwhLS0gYGBgIC0tPgoKIyMgQWNjZXNzaWJpbGl0eSBvZiBtYXJrZXJzClRha2luZyBtYXJrZXJzIGZyb20gRmlnLiBTMiBvZiBKUCdzIG1hbnVzY3JpcHQKYGBge3IsIGZpZy5oZWlnaHQ9MTMsIGZpZy53aWR0aD0xMCwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KdGh5bXVzLm1hcmtlcnMgPC0gYygiUFRQUkMiLCAiQ0QzRyIsICJUWVJPQlAiLCJDRDE5IiwiSE9YQTkiLCdGWFlEMicsIlNIM1RDMSIsIkNDUjkiLCJDRDhBIiwgIkNEOEIiLCJQRENEMSIsICJDUlRBTSIsIkNENDBMRyIsIkNDUjYiLCJGT1hQMyIsIlNPWDEzIiwiWk5GNjgzIiwiS0xSRDEiLCJUTkZTRjExIiwiVlBSRUIxIiwiTVM0QTEiLCAiQ0xFQzlBIiwgIkNMRUMxMEEiLCAiTEFNUDMiLCAiSUwzUkEiLCAiRkNHUjNCIiwgIkMyIiwiVFBTQjIiLAogICAgICAgICAgICAgICAgICAgICdJVEdBMkInLCJHWVBBIiwgIkNESDUiLCAiUkdTNSIsIkNESDEiLCAiUERHRlJBIiwiQ1JBQlAxIikKIyBwYm1jLm1hcmtlcnMgPC0gYygiQ0Q3OUEiLCAiTVM0QTEiLCAiQ0Q4QSIsICJDRDhCIiwgIkxZWiIpCiMgdGh5bXVzLm1hcmtlcnMgPC0gbGlzdChGYj1jKCJQREdGUkEiLCAiQ09MRUMxMSIsICJGQk4xIiwgIlBJMTYiKSwKIyAgICAgICAgICAgICAgICAgICAgICAgIFZTTUM9YygiUERHRlJCIiwgJ0FDVEEyJywgIlJHUzUiKSwKIyAgICAgICAgICAgICAgICAgICAgICAgIEVuZG89YygiUEVDQU0xIiwgIkNESDUiLCJMWVZFMSIpLAojICAgICAgICAgICAgICAgICAgICAgICAgVEVDID0gYygiRVBDQU0iLCAiRk9YTjEiLCAiQ0NMMjUiLCAiQ0NMMTkiKQojICAgICAgICAgICAgICAgICAgICAgICAgKQp0aHltdXMubWFya2Vycy5kZiA8LSBpbWFwKHRoeW11cy5tYXJrZXJzLCB+IGRhdGEuZnJhbWUoZ2VuZT0ueCwgY2VsbC50eXBlLmNsYXNzPS55KSkgJT4lCiAgcHVycnI6OnJlZHVjZShiaW5kX3Jvd3MpCgptYXJrZXIuYWNjZXNzLmRmIDwtIGF0YWMuc2V1QGFzc2F5cyRBQ1RJVklUWUBkYXRhW2ludGVyc2VjdCh0aHltdXMubWFya2Vycywgcm93bmFtZXMoYXRhYy5zZXVAYXNzYXlzJEFDVElWSVRZKSksXSAlPiUKICBhcy5tYXRyaXgoKSAlPiUKICByZXNoYXBlMjo6bWVsdCh2YXJuYW1lcz1jKCJnZW5lIiwgImNlbGwiKSwgdmFsdWUubmFtZT0ibG9nLmNvdW50cyIpICU+JQogIGZ1bGxfam9pbihyb3duYW1lc190b19jb2x1bW4oYXRhYy5zZXVAbWV0YS5kYXRhWywgbGFiZWxfY29sc10sICJjZWxsIikpICU+JQogICMgZnVsbF9qb2luKHRoeW11cy5tYXJrZXJzLmRmKSAlPiUKICBwaXZvdF9sb25nZXIoY29scz1sYWJlbF9jb2xzLCBuYW1lc190byA9ICJtZXRob2QiLCB2YWx1ZXNfdG8gPSAicHJlZGljdGVkLmlkIikgJT4lCiAgZHBseXI6Om11dGF0ZShtZXRob2Q9c3RyX3JlbW92ZShtZXRob2QsIi4rXyIpKSAlPiUKICBmaWx0ZXIobWV0aG9kICVpbiUgYygiQ0NBIiwgIkxpZ2VyIiwgIkNvbm9zIikpIAoKb3JkZXJlZF9jZWxsX3R5cGVzIDwtIGMoIkROIiwgIkRQIChRKSIsICJEUCAoUCkiLCAiU1AgKDEpIiwgIk5LIiwgIklMQzMiLCAiREMiLCAiTWFjIiwgIkVyeSIsICJGaWIiKQoKbWFya2Vyc19wbCA8LSAKICBtYXJrZXIuYWNjZXNzLmRmICU+JQogIG11dGF0ZShwcmVkaWN0ZWQuaWQgPSBjYXNlX3doZW4oc3RyX2RldGVjdChwcmVkaWN0ZWQuaWQsICJDRDgiKSB+ICJDRDgrVCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHN0cl9kZXRlY3QocHJlZGljdGVkLmlkLCAiQ0Q0IikgfiAiQ0Q0K1QiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+IHByZWRpY3RlZC5pZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICApICU+JQogIG11dGF0ZShwcmVkaWN0ZWQuaWQ9ZmFjdG9yKHByZWRpY3RlZC5pZCwgbGV2ZWxzID0gb3JkZXJlZF9jZWxsX3R5cGVzKSkgJT4lCiAgZ3JvdXBfYnkobWV0aG9kLCBwcmVkaWN0ZWQuaWQsIGdlbmUpICU+JQogIGRwbHlyOjptdXRhdGUoZnJhYy5jZWxscz1zdW0obG9nLmNvdW50cyA+IDApL24oKSkgJT4lCiAgIyBmaWx0ZXIobWV0aG9kPT0iQ0NBIikgJT4lCiAgdW5ncm91cCgpICU+JQogIGdncGxvdCggYWVzKCBnZW5lLCBwcmVkaWN0ZWQuaWQpKSArCiAgZ2VvbV9wb2ludChhZXMoc2l6ZT1mcmFjLmNlbGxzLCBjb2xvcj1mcmFjLmNlbGxzKSkgKwogIGZhY2V0X2dyaWQobWV0aG9kfi4sIHNwYWNlPSJmcmVlIiwgc2NhbGVzPSJmcmVlX3giKSArCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQoaGlnaD0iZGFya2JsdWUiLCBsb3c9IndoaXRlIikgKwogICMgc2NhbGVfY29sb3JfdmlyaWRpc19jKCkgKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE2KSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9OTAsIGhqdXN0PTEsIHZqdXN0PTAuNSksCiAgICAgICAgc3RyaXAudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTQ1KSkgCgptYXJrZXJzX3BsIAogIApnZ3NhdmUocGFzdGUwKG91dGRpciwgIlRoeW11c19tYXJrZXJzX2FjY2Vzc2liaWxpdHkucG5nIiksIGhlaWdodCA9IDE2LCB3aWR0aCA9IDEyKQpgYGAKClJlcHJvZHVjaW5nIEZpZy4ySCBvbiBULWNlbGwgZGV2ZWxvcG1lbnQKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0xMn0KdC5jZWxsLm1hcmtlcnMgPC0gbGlzdChrbm93bi5tYXJrZXJzID0gYygiQ0QzNCIsICJJR0xMMSIsICJUUkdDMiIsICJUUkRDIiwgIlBUQ1JBIiwgIlRSQkMyIiwgIlRSQUMiLCAiQ0Q0IiwgIkNEOEEiLCAiQ0Q4QiIpLAogICAgICAgICAgICAgICAgICAgICAgIGNoZW1va2luZS5yZWNlcHRvcnMgPSBjKCJDQ1I5IiwgIkNDUjciKSwKICAgICAgICAgICAgICAgICAgICAgICB0Y3IuYWN0aXZhdGlvbiA9IGMoIkNENSIsICJDRDI3IiksCiAgICAgICAgICAgICAgICAgICAgICAgcHJvbGlmZXJhdGlvbj1jKCJQQ05BIiwgIkNESzEiLCAiTUtJNjciKSwKICAgICAgICAgICAgICAgICAgICAgICBjeWNsaW4uRCA9IGMoIkNDTkQyIiwgIkNDTkQzIiksCiAgICAgICAgICAgICAgICAgICAgICAgcmVjb21iaW5hdGlvbj1jKCJSQUcxIiwgIlJBRzIiKSwKICAgICAgICAgICAgICAgICAgICAgICBhcG9wdG9zaXM9YygiSFJLIiwiQk1GIiwgIlRQNTNJTlAxIiksCiAgICAgICAgICAgICAgICAgICAgICAgc3RhZ2UubWFya2VycyA9IGMoIlNUMTgiLCAiSElWRVAzIiwgIlJHUEQzIiwgIlNNUEQzIiwgIkFRUDMiLCAiUk9SQyIsICJTQVRCMSIsICJUT1gyIikKICAgICAgICAgICAgICAgICAgICAgICApIAp0LmNlbGwubWFya2Vycy5kZiA8LSBpbWFwKHQuY2VsbC5tYXJrZXJzLCB+IGRhdGEuZnJhbWUoZ2VuZT0ueCwgY2VsbC50eXBlLmNsYXNzPS55KSkgJT4lCiAgcHVycnI6OnJlZHVjZShiaW5kX3Jvd3MpCgpvcmRlcmVkLnRjZWxscyA8LSBjKCJETiIsICJEUCAoUCkiLCAiRFAgKFEpIiwiU1AgKDEpIikKCnRjZWxscy5tYXJrZXJzLmRmIDwtIAogIGF0YWMuc2V1QGFzc2F5cyRBQ1RJVklUWUBkYXRhW2ludGVyc2VjdCh1bmxpc3QodC5jZWxsLm1hcmtlcnMpLCByb3duYW1lcyhhdGFjLnNldUBhc3NheXMkQUNUSVZJVFkpKSxdICU+JQogIGFzLm1hdHJpeCgpICU+JQogIHJlc2hhcGUyOjptZWx0KHZhcm5hbWVzPWMoImdlbmUiLCAiY2VsbCIpLCB2YWx1ZS5uYW1lPSJsb2cuY291bnRzIikgJT4lCiAgZnVsbF9qb2luKHJvd25hbWVzX3RvX2NvbHVtbihhdGFjLnNldUBtZXRhLmRhdGFbLCBsYWJlbF9jb2xzXSwgImNlbGwiKSkgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHM9bGFiZWxfY29scywgbmFtZXNfdG8gPSAibWV0aG9kIiwgdmFsdWVzX3RvID0gInByZWRpY3RlZC5pZCIpICU+JQogIGRwbHlyOjptdXRhdGUobWV0aG9kPXN0cl9yZW1vdmUobWV0aG9kLCIuK18iKSkgJT4lCiAgZmlsdGVyKG1ldGhvZCAlaW4lIGMoIkNDQSIsICJMaWdlciIsICJDb25vcyIpKSAlPiUKICBtdXRhdGUocHJlZGljdGVkLmlkPWlmZWxzZShzdHJfZGV0ZWN0KHByZWRpY3RlZC5pZCwgIkNEOCsiKSwgIkNEOCtUIiwgcHJlZGljdGVkLmlkKSkgJT4lCiAgbXV0YXRlKHByZWRpY3RlZC5pZD1pZmVsc2Uoc3RyX2RldGVjdChwcmVkaWN0ZWQuaWQsICJDRDQrIiksICJDRDQrVCIsIHByZWRpY3RlZC5pZCkpICU+JQogIGZpbHRlcihwcmVkaWN0ZWQuaWQgJWluJSBvcmRlcmVkLnRjZWxscykgJT4lCiAgZ3JvdXBfYnkobWV0aG9kLCBwcmVkaWN0ZWQuaWQsIGdlbmUpICU+JQogIGRwbHlyOjptdXRhdGUoZnJhYy5jZWxscz1zdW0obG9nLmNvdW50cyA+IDApL24oKSwgbWVhbi5hY2M9bWVhbihsb2cuY291bnRzKSkgJT4lCiAgdW5ncm91cCgpIAoKdGNlbGxzLm1hcmtlcnMuZGYgJT4lCiAgZnVsbF9qb2luKHQuY2VsbC5tYXJrZXJzLmRmKSAlPiUKICAjIGZpbHRlcihtZXRob2Q9PSJDQ0EiKSAlPiUKICBtdXRhdGUocHJlZGljdGVkLmlkPWZhY3RvcihwcmVkaWN0ZWQuaWQsIGxldmVscz1vcmRlcmVkLnRjZWxscykpICU+JQogIGdncGxvdChhZXMoIHByZWRpY3RlZC5pZCwgZ2VuZSkpICsKICBmYWNldF9ncmlkKGNlbGwudHlwZS5jbGFzc35tZXRob2QsIHNjYWxlcyA9ICJmcmVlX3kiLCBzcGFjZT0iZnJlZSIpICsKICBnZW9tX3BvaW50KGFlcyhzaXplPWZyYWMuY2VsbHMsIGNvbG9yPW1lYW4uYWNjKSkgKwogIHNjYWxlX2NvbG9yX2dyYWRpZW50KGhpZ2g9ImRhcmtibHVlIiwgbG93PSJ3aGl0ZSIpICsKICAjIHNjYWxlX2NvbG9yX2dyYWRpZW50MihtaWRwb2ludCA9IDAuNSkgKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE2KSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9OTAsIGhqdXN0PTEsIHZqdXN0PTAuNSksCiAgICAgICAgc3RyaXAudGV4dC55ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTApKSAKCmdnc2F2ZShwYXN0ZTAob3V0ZGlyLCAidGNlbGxfbWFya2Vycy5wbmciKSwgaGVpZ2h0ID0gMTQsIHdpZHRoID0gMTQpCgpgYGAKCjwhLS0gIyMjIENvbXBhcmUgZmVhdHVyZSBzZWxlY3Rpb24gc3RyYXRlZ3kgKHJlZmVyZW5jZSBiYXNlZCkgLS0+CjwhLS0gYGBge3J9IC0tPgo8IS0tIHNldS5jY2EucmVmIDwtIHJlYWRSRFMoIn4vbW9kZWxzL2xhYmVsVHJhbnNmZXJDQ0FfcmVmZXJlbmNlX2h2Z19GNzRfU0NFbGlzdF8yMDE5MTEwMS5SRFMiKSAtLT4KPCEtLSBzZXUubGlnZXIucmVmIDwtIHJlYWRSRFMoIn4vbW9kZWxzL2xhYmVsVHJhbnNmZXJMaWdlcl9yZWZlcmVuY2VfaHZnX0Y3NF9TQ0VsaXN0XzIwMTkxMTAxLlJEUyIpIC0tPgo8IS0tIHNldS5jb25vcy5yZWYgPC0gcmVhZFJEUygifi9tb2RlbHMvbGFiZWxUcmFuc2ZlckNvbm9zX3JlZmVyZW5jZV9odmdfRjc0X1NDRWxpc3RfMjAxOTExMDEuUkRTIikgLS0+Cgo8IS0tIGludGVncmF0ZV9mZWF0dXJlc19yZWYgPC0gc2Nhbigifi9tb2RlbHMvaW50RmVhdHVyZXNfcmVmZXJlbmNlX2h2Z18yMDAwX0Y3NF9TQ0VsaXN0XzIwMTkxMTAxLnR4dCIsIHdoYXQgPSAiIikgLS0+Cgo8IS0tIGludC5saXN0LnJlZiA8LSBsaXN0KENDQT1zZXUuY2NhLnJlZiwgTGlnZXI9c2V1LmxpZ2VyLnJlZiwgQ29ub3M9c2V1LmNvbm9zLnJlZikgLS0+Cgo8IS0tICMjIEFkZCB0byBhdGFjIFNldXJhdCBvYmplY3QgLS0+CjwhLS0gcHJlZC5jY2EucmVmIDwtIGdldFByZWRpY3RlZExhYmVscyhzZXUuY2NhLnJlZiwgIkNDQV9yZWYiLCBzY29yZS5jb2wgPSAicHJlZGljdGlvbi5zY29yZS5tYXgiKSAtLT4KPCEtLSBwcmVkLmxpZ2VyLnJlZiA8LSBnZXRQcmVkaWN0ZWRMYWJlbHMoc2V1LmxpZ2VyLnJlZiwgIkxpZ2VyX3JlZiIpIC0tPgo8IS0tIHByZWQuY29ub3MucmVmIDwtIGdldFByZWRpY3RlZExhYmVscyhzZXUuY29ub3MucmVmLCAiQ29ub3NfcmVmIikgLS0+Cgo8IS0tIGlmIChhbGwocm93bmFtZXMocHJlZC5jb25vcykgPT0gcm93bmFtZXMocHJlZC5jY2EpKSAmIGFsbChyb3duYW1lcyhwcmVkLmNvbm9zKSA9PSByb3duYW1lcyhwcmVkLmxpZ2VyKSkpIHsgLS0+CjwhLS0gICBhdGFjLnNldSA8LSBBZGRNZXRhRGF0YShhdGFjLnNldSwgbWV0YWRhdGEgPSBjYmluZChwcmVkLmNjYS5yZWYsIHByZWQubGlnZXIucmVmLCBwcmVkLmNvbm9zLnJlZikpIC0tPgo8IS0tIH0gZWxzZSB7IC0tPgo8IS0tICAgc3RvcCgiTm9uIGNvcnJlc3BvbmRpbmcgY2VsbCBuYW1lcyIpIC0tPgo8IS0tIH0gLS0+Cgo8IS0tIGBgYCAtLT4KCjwhLS0gYGBge3IsIGZpZy53aWR0aD0xOSwgZmlnLmhlaWdodD05fSAtLT4KPCEtLSBnZ3B1YnI6OmdnYXJyYW5nZSggLS0+CjwhLS0gICBwbG90bGlzdCA9IGxpc3QoIC0tPgo8IS0tICAgICBEaW1QbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkX0NDQV9yZWYiICAsIGNvbHM9Y2VsbC50eXBlLnBhbCwgbGFiZWw9VFJVRSwgcmVwZWw9VFJVRSkgKyBnZ3RpdGxlKCJDQ0EiKSwgLS0+CjwhLS0gICAgIERpbVBsb3QoYXRhYy5zZXUsIHJlZHVjdGlvbiA9ICJ1bWFwLnNuYXAiLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuaWRfTGlnZXJfcmVmIiwgY29scz1jZWxsLnR5cGUucGFsLCBsYWJlbD1UUlVFLCByZXBlbD1UUlVFKSArIGdndGl0bGUoIkxpZ2VyIiksIC0tPgo8IS0tICAgICBEaW1QbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkX0Nvbm9zX3JlZiIsIGNvbHM9Y2VsbC50eXBlLnBhbCwgbGFiZWw9VFJVRSwgcmVwZWw9VFJVRSkgKyBnZ3RpdGxlKCJDb25vcyIpIC0tPgo8IS0tICAgKSwgLS0+CjwhLS0gICBjb21tb24ubGVnZW5kID0gVFJVRSwgbmNvbD0zLCBucm93PTEgLS0+CjwhLS0gKSAgLS0+CjwhLS0gYGBgIC0tPgoKPCEtLSBgYGB7ciwgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTE2fSAtLT4KPCEtLSBwcmVkLmxhYmVscy5yZWYuZGYgPC0gaW1hcChsaXN0KENDQT1wcmVkLmNjYS5yZWYsIExpZ2VyPXByZWQubGlnZXIucmVmLCBDb25vcz1wcmVkLmNvbm9zLnJlZiksIH4gIC0tPgo8IS0tICAgICAgIHJvd25hbWVzX3RvX2NvbHVtbigueCwgImNlbGwiKSAlPiUgLS0+CjwhLS0gICAgICAgcmVuYW1lX2FsbChmdW5zKHN0cl9yZW1vdmUoLiwgc3RyX2MoIl8iLC55KSkpKSAlPiUgLS0+CjwhLS0gICAgICAgbXV0YXRlKG1ldGhvZD0ueSkgLS0+CjwhLS0gICAgICkgJT4lIC0tPgo8IS0tICAgcHVycnI6OnJlZHVjZShiaW5kX3Jvd3MpICU+JSAtLT4KPCEtLSAgIG11dGF0ZShzY29yZT1pZmVsc2UoaXMubmEoc2NvcmVfcmVmKSwgMCwgc2NvcmVfcmVmKSkgLS0+Cgo8IS0tIGZ1bGxfam9pbiggLS0+CjwhLS0gICBwcmVkLmxhYmVscy5kZiwgLS0+CjwhLS0gICBzZWxlY3QocHJlZC5sYWJlbHMucmVmLmRmLCBjZWxsLCBwcmVkaWN0ZWQuaWRfcmVmLCBzY29yZV9yZWYsIG1ldGhvZCksIC0tPgo8IS0tICAgYnk9YygiY2VsbCIsICJtZXRob2QiKSAtLT4KPCEtLSAgICkgJT4lIC0tPgo8IS0tICAgZ3JvdXBfYnkobWV0aG9kLCBwcmVkaWN0ZWQuaWQpICU+JSAtLT4KPCEtLSAgIG11dGF0ZShuX3ByZWQ9bigpKSAlPiUgLS0+CjwhLS0gICB1bmdyb3VwKCkgJT4lIC0tPgo8IS0tICAgZ3JvdXBfYnkobWV0aG9kLCBwcmVkaWN0ZWQuaWQsIHByZWRpY3RlZC5pZF9yZWYpICU+JSAtLT4KPCEtLSAgIHN1bW1hcmlzZShuPW4oKSwgbl9wcmVkPW1heChuX3ByZWQpKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUoZnJhYz1uL25fcHJlZCkgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyhwcmVkaWN0ZWQuaWQsIHByZWRpY3RlZC5pZF9yZWYpKSArIC0tPgo8IS0tICAgZ2VvbV90aWxlKGFlcyhmaWxsPWZyYWMpKSArIC0tPgo8IS0tICAgZmFjZXRfd3JhcChtZXRob2R+LiwgbnJvdz0xLCBuY29sPTMpICsgLS0+CjwhLS0gICBjb29yZF9maXhlZCgpICsgLS0+CjwhLS0gICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdz0id2hpdGUiLCBoaWdoPSJyZWQiKSArIC0tPgo8IS0tICAgeWxhYigiRmVhdC4gc2VsZWN0aW9uOiByZWZlcmVuY2UgSFZHIikgKyB4bGFiKCJGZWF0LiBzZWxlY3Rpb246IHVuaW9uIEhWRyIpICsgLS0+CjwhLS0gICB0aGVtZV9jb3dwbG90KGZvbnRfc2l6ZSA9IDE2KSArIC0tPgo8IS0tICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9NDUsIGhqdXN0PTEpKSArIC0tPgo8IS0tICAgZ2dzYXZlKHBhc3RlMChvdXRkaXIsICJ1bmlvblZTcmVmZXJlbmNlLnBuZyIpLCBoZWlnaHQgPSAxMiwgd2lkdGg9MTApIC0tPgo8IS0tIGBgYCAtLT4KPCEtLSBgYGB7cn0gLS0+Cgo8IS0tIHNjb3JlLkNDQS5yZWYgPC0gICBpbWFwX2RibChhdGFjLm5uLmxpc3QsIH4gc3VtKHByZWQuY2NhLnJlZlsueCwxXSA9PSBwcmVkLmNjYS5yZWZbLnksMV0pL2spICU+JSBzZXROYW1lcyhuYW1lcyhhdGFjLm5uLmxpc3QpKSAtLT4KPCEtLSBzY29yZS5Db25vcy5yZWYgPC0gaW1hcF9kYmwoYXRhYy5ubi5saXN0LCB+IHN1bShwcmVkLmNvbm9zLnJlZlsueCwxXSA9PSBwcmVkLmNvbm9zLnJlZlsueSwxXSkvaykgJT4lIHNldE5hbWVzKG5hbWVzKGF0YWMubm4ubGlzdCkpIC0tPgo8IS0tIHNjb3JlLkxpZ2VyLnJlZiA8LSBpbWFwX2RibChhdGFjLm5uLmxpc3QsIH4gc3VtKHByZWQubGlnZXIucmVmWy54LDFdID09IHByZWQubGlnZXIucmVmWy55LDFdKS9rKSAlPiUgc2V0TmFtZXMobmFtZXMoYXRhYy5ubi5saXN0KSkgLS0+Cgo8IS0tIGtubl9zY29yZV9yZWZfZGYgPC0gLS0+CjwhLS0gICBhcy5kYXRhLmZyYW1lKGNiaW5kKHNjb3JlLkNvbm9zLnJlZiwgc2NvcmUuTGlnZXIucmVmLCBzY29yZS5DQ0EucmVmKSkgJT4lIC0tPgo8IS0tICAgcm93bmFtZXNfdG9fY29sdW1uKCJjZWxsIikgJT4lIC0tPgo8IS0tICAgcGl2b3RfbG9uZ2VyKGNvbHM9c3RyX3N1YnNldChjb2xuYW1lcyguKSwgInNjb3JlIiksIG5hbWVzX3RvID0gIm1ldGhvZCIsIHZhbHVlc190byA9ICJLTk5fc2NvcmUiKSAlPiUgLS0+CjwhLS0gICBkcGx5cjo6bXV0YXRlKEtOTl9zY29yZT1pZmVsc2UoaXMubmEoS05OX3Njb3JlKSwgMCwgS05OX3Njb3JlKSwgLS0+CjwhLS0gICAgICAgICAgICAgICAgIG1ldGhvZD1zdHJfcmVtb3ZlKG1ldGhvZCwgInNjb3JlLiIpKSAtLT4KCjwhLS0gcXVhbnRzID0gc2VxKDAsMSwgYnkgPSAwLjA1KSAtLT4KPCEtLSBBVUVDREZfa25uX3Njb3JlIDwtIGtubl9zY29yZV9yZWZfZGYgJT4lIC0tPgo8IS0tICAgc3BsaXQoLiRtZXRob2QpICU+JSAtLT4KPCEtLSAgIG1hcF9kYmwoIH4gLnggJT4lIC0tPgo8IS0tICAgICAgIGFycmFuZ2UoS05OX3Njb3JlKSAlPiUgIC0tPgo8IS0tICAgICAgIHtlY2RmKC4kS05OX3Njb3JlKShxdWFudHMpfSAlPiUgQVVDKHF1YW50cywuKSAtLT4KPCEtLSAgICAgKSAtLT4KCjwhLS0ga25uX3Njb3JlX3JlZl9kZiAlPiUgLS0+CjwhLS0gICBtdXRhdGUoQVVDPUFVRUNERl9rbm5fc2NvcmVbbWV0aG9kXSkgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyhLTk5fc2NvcmUsIGNvbG9yPW1ldGhvZCwgZmlsbD1tZXRob2QpKSArIC0tPgo8IS0tICAgc3RhdF9lY2RmKHNpemU9MSkgKyAtLT4KPCEtLSAgIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIlNldDEiKSArIC0tPgo8IS0tICAgZ2VvbV90ZXh0KGRhdGE9LiAlPiUgZ3JvdXBfYnkobWV0aG9kKSAlPiUgc3VtbWFyaXNlKEFVQz1tYXgoQVVDKSksICAtLT4KPCEtLSAgICAgICAgICAgICB4PTAuMDUsIGhqdXN0PTAsIC0tPgo8IS0tICAgICAgICAgICAgIGFlcyhsYWJlbD1nbHVlKCJBVUMgPSB7cm91bmQoQVVDLCAzKX0iKSwgeT1jKDAuOTAsIDAuOTUsIDEpKSkgKyAtLT4KPCEtLSAgIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE2KSArIC0tPgo8IS0tICAgeWxhYigiRUNERiIpICAtLT4KPCEtLSBgYGAgLS0+Cgo8IS0tICMjIyBJcyB0aGUgdW5pb24gb3IgdGhlIHJlZmVyZW5jZSBiZXN0IG1haW50YWluaW5nIHRoZSBzdHJ1Y3R1cmUgb2YgdGhlIEFUQUM/IC0tPgo8IS0tIGBgYHtyLCBmaWcud2lkdGg9MTUsZmlnLmhlaWdodD03fSAtLT4KPCEtLSBrID0gNTAgLS0+CjwhLS0gYXRhYy5zZXUgPC0gRmluZE5laWdoYm9ycyhhdGFjLnNldSwgYXNzYXkgPSAiQVRBQyIsIHJlZHVjdGlvbiA9ICJTbmFwQVRBQyIsIGRpbXMgPSAxOjE1LCBrLnBhcmFtID0gaykgLS0+Cgo8IS0tIGF0YWMubm4ubGlzdCA8LSBnZXROTmxpc3QoYXRhYy5zZXUpIC0tPgoKPCEtLSBzY29yZS5DQ0EgPC0gaW1hcF9kYmwoYXRhYy5ubi5saXN0LCB+IHN1bShwcmVkLmNjYVsueCwxXSA9PSBwcmVkLmNjYVsueSwxXSkvaykgJT4lIHNldE5hbWVzKG5hbWVzKGF0YWMubm4ubGlzdCkpIC0tPgo8IS0tIHNjb3JlLkNvbm9zIDwtIGltYXBfZGJsKGF0YWMubm4ubGlzdCwgfiBzdW0ocHJlZC5jb25vc1sueCwxXSA9PSBwcmVkLmNvbm9zWy55LDFdKS9rKSAlPiUgc2V0TmFtZXMobmFtZXMoYXRhYy5ubi5saXN0KSkgLS0+CjwhLS0gc2NvcmUuTGlnZXIgPC0gaW1hcF9kYmwoYXRhYy5ubi5saXN0LCB+IHN1bShwcmVkLmxpZ2VyWy54LDFdID09IHByZWQubGlnZXJbLnksMV0pL2spICU+JSBzZXROYW1lcyhuYW1lcyhhdGFjLm5uLmxpc3QpKSAtLT4KCjwhLS0ga25uX3Njb3JlX2RmIDwtIC0tPgo8IS0tICAgYXMuZGF0YS5mcmFtZShjYmluZChzY29yZS5Db25vcywgc2NvcmUuTGlnZXIsIHNjb3JlLkNDQSkpICU+JSAtLT4KPCEtLSAgIHJvd25hbWVzX3RvX2NvbHVtbigiY2VsbCIpICU+JSAtLT4KPCEtLSAgIHBpdm90X2xvbmdlcihjb2xzPXN0cl9zdWJzZXQoY29sbmFtZXMoLiksICJzY29yZSIpLCBuYW1lc190byA9ICJtZXRob2QiLCB2YWx1ZXNfdG8gPSAiS05OX3Njb3JlIikgJT4lIC0tPgo8IS0tICAgZHBseXI6Om11dGF0ZShLTk5fc2NvcmU9aWZlbHNlKGlzLm5hKEtOTl9zY29yZSksIDAsIEtOTl9zY29yZSksIC0tPgo8IS0tICAgICAgICAgICAgICAgICBtZXRob2Q9c3RyX3JlbW92ZShtZXRob2QsICJzY29yZS4iKSkgLS0+CgoKPCEtLSBzY29yZS5DQ0EucmVmIDwtICAgaW1hcF9kYmwoYXRhYy5ubi5saXN0LCB+IHN1bShwcmVkLmNjYS5yZWZbLngsMV0gPT0gcHJlZC5jY2EucmVmWy55LDFdKS9rKSAlPiUgc2V0TmFtZXMobmFtZXMoYXRhYy5ubi5saXN0KSkgLS0+CjwhLS0gc2NvcmUuQ29ub3MucmVmIDwtIGltYXBfZGJsKGF0YWMubm4ubGlzdCwgfiBzdW0ocHJlZC5jb25vcy5yZWZbLngsMV0gPT0gcHJlZC5jb25vcy5yZWZbLnksMV0pL2spICU+JSBzZXROYW1lcyhuYW1lcyhhdGFjLm5uLmxpc3QpKSAtLT4KPCEtLSBzY29yZS5MaWdlci5yZWYgPC0gaW1hcF9kYmwoYXRhYy5ubi5saXN0LCB+IHN1bShwcmVkLmxpZ2VyLnJlZlsueCwxXSA9PSBwcmVkLmxpZ2VyLnJlZlsueSwxXSkvaykgJT4lIHNldE5hbWVzKG5hbWVzKGF0YWMubm4ubGlzdCkpIC0tPgoKPCEtLSBrbm5fc2NvcmVfcmVmX2RmIDwtIC0tPgo8IS0tICAgYXMuZGF0YS5mcmFtZShjYmluZChzY29yZS5Db25vcy5yZWYsIHNjb3JlLkxpZ2VyLnJlZiwgc2NvcmUuQ0NBLnJlZikpICU+JSAtLT4KPCEtLSAgIHJvd25hbWVzX3RvX2NvbHVtbigiY2VsbCIpICU+JSAtLT4KPCEtLSAgIHBpdm90X2xvbmdlcihjb2xzPXN0cl9zdWJzZXQoY29sbmFtZXMoLiksICJzY29yZSIpLCBuYW1lc190byA9ICJtZXRob2QiLCB2YWx1ZXNfdG8gPSAiS05OX3Njb3JlIikgJT4lIC0tPgo8IS0tICAgZHBseXI6Om11dGF0ZShLTk5fc2NvcmU9aWZlbHNlKGlzLm5hKEtOTl9zY29yZSksIDAsIEtOTl9zY29yZSksIC0tPgo8IS0tICAgICAgICAgICAgICAgICBtZXRob2Q9c3RyX3JlbW92ZShtZXRob2QsICJzY29yZS4iKSkgLS0+CgoKPCEtLSBiaW5kX3Jvd3Moa25uX3Njb3JlX2RmLCBrbm5fc2NvcmVfcmVmX2RmKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUoZmVhdHVyZS5zZWxlY3Rpb249aWZlbHNlKHN0cl9kZXRlY3QobWV0aG9kLCAicmVmIiksICJyZWYiLCAidW5pb24iKSkgJT4lIC0tPgo8IS0tICAgbXV0YXRlKG1ldGhvZD1zdHJfcmVtb3ZlKG1ldGhvZCwgIi5yZWYiKSkgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyhLTk5fc2NvcmUsIGNvbG9yPWZlYXR1cmUuc2VsZWN0aW9uLCBmaWxsPW1ldGhvZCkpICsgLS0+CjwhLS0gICBzdGF0X2VjZGYoc2l6ZT0xKSArIC0tPgo8IS0tICAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpICsgLS0+CjwhLS0gICBmYWNldF93cmFwKG1ldGhvZH4uKSArIC0tPgo8IS0tICAgdGhlbWVfYncoYmFzZV9zaXplID0gMTYpICsgLS0+CjwhLS0gICB5bGFiKCJFQ0RGIikgKyAtLT4KPCEtLSAgIGdndGl0bGUocGFzdGUoIksgPSIsIGspKSArIC0tPgo8IS0tICAgZ2dzYXZlKHBhc3RlMChvdXRkaXIsICJ1bmlvblZTcmVmZXJlbmNlX0tOTi5wbmciKSwgaGVpZ2h0ID0gNCwgd2lkdGggPSAxMCkgLS0+Cgo8IS0tIGBgYCAtLT4KCjwhLS0gLS0tIC0tPgoKPCEtLSBgYGB7cn0gLS0+CjwhLS0gcGxvdGx5OjpnZ3Bsb3RseShEaW1QbG90KG9yaWcuUk5BLnNldSwgcmVkdWN0aW9uID0gInVtYXAuc25hcCIsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5pZF9DQ0EiKSkgLS0+CjwhLS0gcGxvdGx5OjpnZ3Bsb3RseShEaW1QbG90KGF0YWMuc2V1LCByZWR1Y3Rpb24gPSAidW1hcC5zbmFwIiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkX0NDQSIpKSAtLT4KPCEtLSBgYGAgLS0+CgoKIyMjIFRob3VnaHRzCi0gQ29ub3Mgc2NvcmVzIGEgbG90IG9mIGNlbGxzIHdpdGggaGlnaCBjb25maWRlbmNlLCBidXQgZmFpbHMgdG8gYXNzaWduIGNlbGxzIHRvIGRpZmZpY3VsdCBjbHVzdGVycyAKLSBDQ0EgcmVzZW1ibGVzIHRoZSBjb21wb3NpdGlvbiBvZiB0aGUgUk5BIGRhdGEgYmV0dGVyLCBidXQgY3VyaW91cyB0aGF0IHRoZSBvdGhlciBtZXRob2RzIGlkZW50aWZ5IHdheSBtb3JlIAoKCgoKCgoKCgoK